use ratex_types::PathCommand;

/// Returns (commands, width, height, fill) for a KaTeX SVG accent, or `None` if unhandled.
///
/// `group_len` is KaTeX’s `groupLength(base)` (`ordgroup.body.length`) for `\\widehat` /
/// `\\widecheck` / `\\widetilde` / `\\utilde` — variant choice uses this, not base width.
pub fn katex_accent_path(
    label: &str,
    base_width_em: f64,
    group_len: usize,
) -> Option<(Vec<PathCommand>, f64, f64, bool)> {
    match label {
        "\\vec" => {
            // KaTeX `svgGeometry.path.vec` (glyph U+20D7) + `svgData.vec` fixed size — not stretched to base width.
            const W_EM: f64 = 0.471;
            const H_EM: f64 = 0.714;
            let raw = parse_svg_path(KATEX_VEC_PATH);
            let cmds = scale_svg_path_thousandths(&raw);
            Some((cmds, W_EM, H_EM, true))
        }
        "\\widehat" | "\\widecheck" => {
            let is_hat = label == "\\widehat";
            let (path, vb_w, vb_h, h_em) = select_hat_check(is_hat, group_len);
            let cmds = parse_and_fit_nonuniform(path, vb_w, vb_h, base_width_em, h_em);
            Some((cmds, base_width_em, h_em, true))
        }
        "\\widetilde" | "\\utilde" => {
            let (path, vb_w, vb_h, h_em) = select_tilde(group_len);
            let cmds = parse_and_fit_nonuniform(path, vb_w, vb_h, base_width_em, h_em);
            Some((cmds, base_width_em, h_em, true))
        }
        "\\overgroup" => {
            // KaTeX: viewBoxHeight=342, height=0.342
            let h_em = 0.342;
            let cmds = build_overgroup(base_width_em, h_em, true);
            Some((cmds, base_width_em, h_em, true))
        }
        "\\undergroup" => {
            let h_em = 0.342;
            let cmds = build_overgroup(base_width_em, h_em, false);
            Some((cmds, base_width_em, h_em, true))
        }
        _ => None,
    }
}

/// Returns path commands for KaTeX stretchy arrows. Delegates to `katex_stretchy_path`.
/// Kept for backward compatibility with existing callers.
pub fn katex_stretchy_arrow_path(
    label: &str,
    width_em: f64,
    _height_em: f64,
) -> Option<Vec<PathCommand>> {
    katex_stretchy_path(label, width_em).map(|(cmds, _)| cmds)
}

fn scale_cmd_twohead_uniform(cmd: &PathCommand, s: f64, vb_cy: f64, x_shift: f64) -> PathCommand {
    match *cmd {
        PathCommand::MoveTo { x, y } => PathCommand::MoveTo {
            x: x * s + x_shift,
            y: (y - vb_cy) * s,
        },
        PathCommand::LineTo { x, y } => PathCommand::LineTo {
            x: x * s + x_shift,
            y: (y - vb_cy) * s,
        },
        PathCommand::CubicTo {
            x1,
            y1,
            x2,
            y2,
            x,
            y,
        } => PathCommand::CubicTo {
            x1: x1 * s + x_shift,
            y1: (y1 - vb_cy) * s,
            x2: x2 * s + x_shift,
            y2: (y2 - vb_cy) * s,
            x: x * s + x_shift,
            y: (y - vb_cy) * s,
        },
        PathCommand::QuadTo { x1, y1, x, y } => PathCommand::QuadTo {
            x1: x1 * s + x_shift,
            y1: (y1 - vb_cy) * s,
            x: x * s + x_shift,
            y: (y - vb_cy) * s,
        },
        PathCommand::Close => PathCommand::Close,
    }
}

/// Clip path to rectangle [x_min, x_max] x [y_min, y_max]. Curves are flattened then clipped.
/// For filled closed contours, the clip boundary edges are implicitly added by emitting
/// LineTo(a) whenever the start of a newly-visible segment doesn't match the current position.
fn clip_path_to_rect(
    commands: &[PathCommand],
    x_min: f64,
    x_max: f64,
    y_min: f64,
    y_max: f64,
) -> Vec<PathCommand> {
    let contours = flatten_path_to_contours(commands);
    let mut out = Vec::new();
    for contour in contours {
        if contour.len() < 2 {
            continue;
        }
        let mut need_move = true;
        let mut last_pt: (f64, f64) = (f64::NAN, f64::NAN);

        let n = contour.len();
        // Process all n edges: 0→1, 1→2, …, (n-1)→0 (closing edge).
        for i in 0..n {
            let p0 = contour[i];
            let p1 = contour[(i + 1) % n];
            let segments = clip_segment_to_rect(p0, p1, x_min, x_max, y_min, y_max);
            for (a, b) in segments {
                if need_move {
                    out.push(PathCommand::MoveTo { x: a.0, y: a.1 });
                    need_move = false;
                } else if (a.0 - last_pt.0).abs() > 1e-9 || (a.1 - last_pt.1).abs() > 1e-9 {
                    // Gap due to clipped-away segments: emit the clip-boundary start point
                    // so the contour remains closed and the clip edge is explicitly drawn.
                    out.push(PathCommand::LineTo { x: a.0, y: a.1 });
                }
                out.push(PathCommand::LineTo { x: b.0, y: b.1 });
                last_pt = b;
            }
        }
    }
    out
}

fn flatten_path_to_contours(commands: &[PathCommand]) -> Vec<Vec<(f64, f64)>> {
    let mut contours = Vec::new();
    let mut current = Vec::new();
    let mut last = (0.0_f64, 0.0_f64);
    const N: usize = 16; // steps per curve

    for cmd in commands {
        match *cmd {
            PathCommand::MoveTo { x, y } => {
                if !current.is_empty() {
                    contours.push(std::mem::take(&mut current));
                }
                last = (x, y);
                current.push(last);
            }
            PathCommand::LineTo { x, y } => {
                last = (x, y);
                current.push(last);
            }
            PathCommand::CubicTo {
                x1,
                y1,
                x2,
                y2,
                x,
                y,
            } => {
                let (x0, y0) = last;
                for k in 1..=N {
                    let t = k as f64 / N as f64;
                    let u = 1.0 - t;
                    let x = u * u * u * x0
                        + 3.0 * u * u * t * x1
                        + 3.0 * u * t * t * x2
                        + t * t * t * x;
                    let y = u * u * u * y0
                        + 3.0 * u * u * t * y1
                        + 3.0 * u * t * t * y2
                        + t * t * t * y;
                    last = (x, y);
                    current.push(last);
                }
            }
            PathCommand::QuadTo { x1, y1, x, y } => {
                let (x0, y0) = last;
                for k in 1..=N {
                    let t = k as f64 / N as f64;
                    let u = 1.0 - t;
                    let x = u * u * x0 + 2.0 * u * t * x1 + t * t * x;
                    let y = u * u * y0 + 2.0 * u * t * y1 + t * t * y;
                    last = (x, y);
                    current.push(last);
                }
            }
            PathCommand::Close => {
                if !current.is_empty() {
                    contours.push(std::mem::take(&mut current));
                }
            }
        }
    }
    if !current.is_empty() {
        contours.push(current);
    }
    contours
}

/// Clip line segment (p0,p1) to rectangle. Returns 0 or 1 segment (as (start,end)) inside the rect.
/// Uses Liang-Barsky.
fn clip_segment_to_rect(
    p0: (f64, f64),
    p1: (f64, f64),
    x_min: f64,
    x_max: f64,
    y_min: f64,
    y_max: f64,
) -> Vec<((f64, f64), (f64, f64))> {
    let (x0, y0) = p0;
    let (x1, y1) = p1;
    let dx = x1 - x0;
    let dy = y1 - y0;
    let mut t0 = 0.0_f64;
    let mut t1 = 1.0_f64;

    let mut clip = |p: f64, q: f64| -> bool {
        if p.abs() < 1e-12 {
            return q >= 0.0;
        }
        let t = q / p;
        if p < 0.0 {
            if t > t1 {
                return false;
            }
            if t > t0 {
                t0 = t;
            }
        } else {
            if t < t0 {
                return false;
            }
            if t < t1 {
                t1 = t;
            }
        }
        true
    };

    if !clip(-dx, x0 - x_min) {
        return vec![];
    }
    if !clip(dx, x_max - x0) {
        return vec![];
    }
    if !clip(-dy, y0 - y_min) {
        return vec![];
    }
    if !clip(dy, y_max - y0) {
        return vec![];
    }

    if t0 >= t1 - 1e-9 {
        return vec![];
    }
    let a = (x0 + t0 * dx, y0 + t0 * dy);
    let b = (x0 + t1 * dx, y0 + t1 * dy);
    vec![(a, b)]
}

/// KaTeX `svgGeometry.js` path `vec` (from Main U+20D7); viewBox `0 0 471 714` (= 1000×0.471 by 1000×0.714).
const KATEX_VEC_PATH: &str = "M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5 3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11 10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63 -1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1 -7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59 H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359 c-16-25.333-24-45-24-59z";

fn scale_svg_path_thousandths(cmds: &[PathCommand]) -> Vec<PathCommand> {
    let s = 0.001;
    cmds.iter()
        .map(|c| match *c {
            PathCommand::MoveTo { x, y } => PathCommand::MoveTo { x: x * s, y: y * s },
            PathCommand::LineTo { x, y } => PathCommand::LineTo { x: x * s, y: y * s },
            PathCommand::CubicTo {
                x1,
                y1,
                x2,
                y2,
                x,
                y,
            } => PathCommand::CubicTo {
                x1: x1 * s,
                y1: y1 * s,
                x2: x2 * s,
                y2: y2 * s,
                x: x * s,
                y: y * s,
            },
            PathCommand::QuadTo { x1, y1, x, y } => PathCommand::QuadTo {
                x1: x1 * s,
                y1: y1 * s,
                x: x * s,
                y: y * s,
            },
            PathCommand::Close => PathCommand::Close,
        })
        .collect()
}

/// KaTeX `svgSpan` / `svgGeometry.js`: `imgIndex = [1,1,2,2,3,3][numChars]` for `numChars` ≤ 5;
/// `numChars > 5` uses the fourth (widest/tallest) asset.
fn wide_accent_img_index(group_len: usize) -> usize {
    if group_len > 5 {
        4
    } else {
        [1, 1, 2, 2, 3, 3][group_len]
    }
}

fn select_hat_check(is_hat: bool, group_len: usize) -> (&'static str, f64, f64, f64) {
    let img_index = wide_accent_img_index(group_len);
    let prefix = if is_hat { &WIDEHAT } else { &WIDECHECK };
    let vb_w = [0.0, 1062.0, 2364.0, 2364.0, 2364.0][img_index];
    let vb_h = [0.0, 239.0, 300.0, 360.0, 420.0][img_index];
    // KaTeX: `_height = [0, 0.24, 0.3, 0.3, 0.36, 0.42][imgIndex]`; `numChars > 5` forces 0.42.
    let h_em = if group_len > 5 {
        0.42
    } else {
        [0.0, 0.24, 0.3, 0.3, 0.36, 0.42][img_index]
    };
    (prefix[img_index - 1], vb_w, vb_h, h_em)
}

fn select_tilde(group_len: usize) -> (&'static str, f64, f64, f64) {
    let img_index = wide_accent_img_index(group_len);
    let vb_w = [0.0, 600.0, 1033.0, 2339.0, 2340.0][img_index];
    let vb_h = [0.0, 260.0, 286.0, 306.0, 312.0][img_index];
    let h_em = if group_len > 5 {
        0.34
    } else {
        [0.0, 0.26, 0.286, 0.3, 0.306, 0.34][img_index]
    };
    (TILDE[img_index - 1], vb_w, vb_h, h_em)
}

/// Parse SVG path and scale with independent X/Y factors (preserveAspectRatio="none").
fn parse_and_fit_nonuniform(
    svg_path: &str,
    vb_width: f64,
    vb_height: f64,
    target_width_em: f64,
    target_height_em: f64,
) -> Vec<PathCommand> {
    let raw = parse_svg_path(svg_path);
    let sx = target_width_em / vb_width;
    let sy = target_height_em / vb_height;
    raw.iter().map(|c| scale_cmd_xy(c, sx, sy)).collect()
}

/// Build the overgroup/undergroup filled path from KaTeX leftgroup/rightgroup data.
fn build_overgroup(width_em: f64, h_em: f64, is_over: bool) -> Vec<PathCommand> {
    // KaTeX overgroup: left half = leftgroup, right half = rightgroup
    // viewBox: 400000 × 342, height = 0.342em
    // We combine both halves into a single filled path.
    //
    // leftgroup outer curve (in viewBox units):
    //   (435, 80) → C(64,80)(168.3,229.4)(21,260) → small curves → (0,257) → V(0,219)
    //   Inner: (0,219) → C(76,61)(257,0)(435,0) → H to right → close
    //
    // rightgroup: mirror at the right end.
    let vb_h = 342.0;
    let sy = h_em / vb_h;

    // KaTeX's curves span 0..435 viewBox units at each end.
    // At 1000:1 scale, 435 units = 0.435em. Keep this natural size.
    // If the target is too narrow, shrink corners proportionally.
    let natural_corner: f64 = 0.435;
    let corner_em = natural_corner.min(width_em / 2.0);
    // cx converts curve x-coordinates (0..435) to em
    let cx = corner_em / 435.0;

    let ly = |y: f64| y * sy;

    if is_over {
        vec![
            // Start at top-left, after corner
            PathCommand::MoveTo {
                x: corner_em,
                y: ly(80.0),
            },
            // Left outer curve down
            PathCommand::CubicTo {
                x1: 64.0 * cx,
                y1: ly(80.0),
                x2: 168.3 * cx,
                y2: ly(229.4),
                x: 21.0 * cx,
                y: ly(260.0),
            },
            PathCommand::CubicTo {
                x1: 15.1 * cx,
                y1: ly(261.2),
                x2: 3.0 * cx,
                y2: ly(260.0),
                x: 3.0 * cx,
                y: ly(260.0),
            },
            PathCommand::CubicTo {
                x1: 1.0 * cx,
                y1: ly(260.0),
                x2: 0.0,
                y2: ly(259.0),
                x: 0.0,
                y: ly(257.0),
            },
            PathCommand::LineTo {
                x: 0.0,
                y: ly(219.0),
            },
            // Left inner curve up
            PathCommand::CubicTo {
                x1: 76.0 * cx,
                y1: ly(61.0),
                x2: 257.0 * cx,
                y2: ly(0.0),
                x: corner_em,
                y: ly(0.0),
            },
            // Bottom flat section
            PathCommand::LineTo {
                x: width_em - corner_em,
                y: ly(0.0),
            },
            // Right inner curve down
            PathCommand::CubicTo {
                x1: width_em - 257.0 * cx,
                y1: ly(0.0),
                x2: width_em - 76.0 * cx,
                y2: ly(61.0),
                x: width_em,
                y: ly(219.0),
            },
            PathCommand::LineTo {
                x: width_em,
                y: ly(257.0),
            },
            // Right outer curves up
            PathCommand::CubicTo {
                x1: width_em,
                y1: ly(259.0),
                x2: width_em - 1.0 * cx,
                y2: ly(260.0),
                x: width_em - 3.0 * cx,
                y: ly(260.0),
            },
            PathCommand::CubicTo {
                x1: width_em - 3.0 * cx,
                y1: ly(260.0),
                x2: width_em - 15.1 * cx,
                y2: ly(261.2),
                x: width_em - 21.0 * cx,
                y: ly(260.0),
            },
            PathCommand::CubicTo {
                x1: width_em - 168.3 * cx,
                y1: ly(229.4),
                x2: width_em - 64.0 * cx,
                y2: ly(80.0),
                x: width_em - corner_em,
                y: ly(80.0),
            },
            PathCommand::Close,
        ]
    } else {
        // undergroup: KaTeX leftgroupunder / rightgroupunder
        // leftgroupunder:
        //   (435, 262) → C(64,262)(168.3,112.6)(21,82) → curves → (0,85) → V(0,123)
        //   Inner: (0,123) → C(76,281)(257,342)(435,342) → H → close
        vec![
            PathCommand::MoveTo {
                x: corner_em,
                y: ly(262.0),
            },
            PathCommand::CubicTo {
                x1: 64.0 * cx,
                y1: ly(262.0),
                x2: 168.3 * cx,
                y2: ly(112.6),
                x: 21.0 * cx,
                y: ly(82.0),
            },
            PathCommand::CubicTo {
                x1: 15.1 * cx,
                y1: ly(80.8),
                x2: 3.0 * cx,
                y2: ly(82.0),
                x: 3.0 * cx,
                y: ly(82.0),
            },
            PathCommand::CubicTo {
                x1: 1.0 * cx,
                y1: ly(82.0),
                x2: 0.0,
                y2: ly(83.0),
                x: 0.0,
                y: ly(85.0),
            },
            PathCommand::LineTo {
                x: 0.0,
                y: ly(123.0),
            },
            PathCommand::CubicTo {
                x1: 76.0 * cx,
                y1: ly(281.0),
                x2: 257.0 * cx,
                y2: ly(342.0),
                x: corner_em,
                y: ly(342.0),
            },
            PathCommand::LineTo {
                x: width_em - corner_em,
                y: ly(342.0),
            },
            PathCommand::CubicTo {
                x1: width_em - 257.0 * cx,
                y1: ly(342.0),
                x2: width_em - 76.0 * cx,
                y2: ly(281.0),
                x: width_em,
                y: ly(123.0),
            },
            PathCommand::LineTo {
                x: width_em,
                y: ly(85.0),
            },
            PathCommand::CubicTo {
                x1: width_em,
                y1: ly(83.0),
                x2: width_em - 1.0 * cx,
                y2: ly(82.0),
                x: width_em - 3.0 * cx,
                y: ly(82.0),
            },
            PathCommand::CubicTo {
                x1: width_em - 3.0 * cx,
                y1: ly(82.0),
                x2: width_em - 15.1 * cx,
                y2: ly(80.8),
                x: width_em - 21.0 * cx,
                y: ly(82.0),
            },
            PathCommand::CubicTo {
                x1: width_em - 168.3 * cx,
                y1: ly(112.6),
                x2: width_em - 64.0 * cx,
                y2: ly(262.0),
                x: width_em - corner_em,
                y: ly(262.0),
            },
            PathCommand::Close,
        ]
    }
}

fn scale_cmd_xy(cmd: &PathCommand, sx: f64, sy: f64) -> PathCommand {
    match *cmd {
        PathCommand::MoveTo { x, y } => PathCommand::MoveTo {
            x: x * sx,
            y: y * sy,
        },
        PathCommand::LineTo { x, y } => PathCommand::LineTo {
            x: x * sx,
            y: y * sy,
        },
        PathCommand::CubicTo {
            x1,
            y1,
            x2,
            y2,
            x,
            y,
        } => PathCommand::CubicTo {
            x1: x1 * sx,
            y1: y1 * sy,
            x2: x2 * sx,
            y2: y2 * sy,
            x: x * sx,
            y: y * sy,
        },
        PathCommand::QuadTo { x1, y1, x, y } => PathCommand::QuadTo {
            x1: x1 * sx,
            y1: y1 * sy,
            x: x * sx,
            y: y * sy,
        },
        PathCommand::Close => PathCommand::Close,
    }
}

// ---------------------------------------------------------------------------
// Minimal SVG path data parser
// ---------------------------------------------------------------------------

/// Parse a KaTeX-style SVG path `d` string into [`PathCommand`]s (for delimiters, accents).
pub(crate) fn parse_svg_path_data(d: &str) -> Vec<PathCommand> {
    parse_svg_path(d)
}

fn parse_svg_path(d: &str) -> Vec<PathCommand> {
    let tokens = tokenize_svg(d);
    let mut cmds = Vec::new();
    let mut i = 0;
    let mut cx = 0.0_f64;
    let mut cy = 0.0_f64;
    let mut subpath_start = (0.0_f64, 0.0_f64);
    let mut last_cmd = b'M';

    while i < tokens.len() {
        let cmd_byte = match &tokens[i] {
            Token::Cmd(c) => {
                i += 1;
                *c
            }
            Token::Num(_) => last_cmd,
        };

        match cmd_byte {
            b'M' => {
                let (x, y) = read2(&tokens, &mut i);
                cx = x;
                cy = y;
                subpath_start = (cx, cy);
                cmds.push(PathCommand::MoveTo { x, y });
                last_cmd = b'L';
            }
            b'm' => {
                let (dx, dy) = read2(&tokens, &mut i);
                cx += dx;
                cy += dy;
                subpath_start = (cx, cy);
                cmds.push(PathCommand::MoveTo { x: cx, y: cy });
                last_cmd = b'l';
            }
            b'L' => {
                let (x, y) = read2(&tokens, &mut i);
                cx = x;
                cy = y;
                cmds.push(PathCommand::LineTo { x, y });
                last_cmd = b'L';
            }
            b'l' => {
                let (dx, dy) = read2(&tokens, &mut i);
                cx += dx;
                cy += dy;
                cmds.push(PathCommand::LineTo { x: cx, y: cy });
                last_cmd = b'l';
            }
            b'H' => {
                let x = read1(&tokens, &mut i);
                cx = x;
                cmds.push(PathCommand::LineTo { x: cx, y: cy });
                last_cmd = b'H';
            }
            b'h' => {
                let dx = read1(&tokens, &mut i);
                cx += dx;
                cmds.push(PathCommand::LineTo { x: cx, y: cy });
                last_cmd = b'h';
            }
            b'V' => {
                let y = read1(&tokens, &mut i);
                cy = y;
                cmds.push(PathCommand::LineTo { x: cx, y: cy });
                last_cmd = b'V';
            }
            b'v' => {
                let dy = read1(&tokens, &mut i);
                cy += dy;
                cmds.push(PathCommand::LineTo { x: cx, y: cy });
                last_cmd = b'v';
            }
            b'C' => {
                let (x1, y1) = read2(&tokens, &mut i);
                let (x2, y2) = read2(&tokens, &mut i);
                let (x, y) = read2(&tokens, &mut i);
                cx = x;
                cy = y;
                cmds.push(PathCommand::CubicTo {
                    x1,
                    y1,
                    x2,
                    y2,
                    x,
                    y,
                });
                last_cmd = b'C';
            }
            b'c' => {
                let (dx1, dy1) = read2(&tokens, &mut i);
                let (dx2, dy2) = read2(&tokens, &mut i);
                let (dx, dy) = read2(&tokens, &mut i);
                let x1 = cx + dx1;
                let y1 = cy + dy1;
                let x2 = cx + dx2;
                let y2 = cy + dy2;
                cx += dx;
                cy += dy;
                cmds.push(PathCommand::CubicTo {
                    x1,
                    y1,
                    x2,
                    y2,
                    x: cx,
                    y: cy,
                });
                last_cmd = b'c';
            }
            b'S' => {
                let (x2, y2) = read2(&tokens, &mut i);
                let (x, y) = read2(&tokens, &mut i);
                let x1 = cx;
                let y1 = cy;
                cx = x;
                cy = y;
                cmds.push(PathCommand::CubicTo {
                    x1,
                    y1,
                    x2,
                    y2,
                    x,
                    y,
                });
                last_cmd = b'S';
            }
            b's' => {
                let (dx2, dy2) = read2(&tokens, &mut i);
                let (dx, dy) = read2(&tokens, &mut i);
                let x1 = cx;
                let y1 = cy;
                let x2 = cx + dx2;
                let y2 = cy + dy2;
                cx += dx;
                cy += dy;
                cmds.push(PathCommand::CubicTo {
                    x1,
                    y1,
                    x2,
                    y2,
                    x: cx,
                    y: cy,
                });
                last_cmd = b's';
            }
            b'Q' => {
                let (x1, y1) = read2(&tokens, &mut i);
                let (x, y) = read2(&tokens, &mut i);
                cx = x;
                cy = y;
                cmds.push(PathCommand::QuadTo { x1, y1, x, y });
                last_cmd = b'Q';
            }
            b'q' => {
                let (dx1, dy1) = read2(&tokens, &mut i);
                let (dx, dy) = read2(&tokens, &mut i);
                let x1 = cx + dx1;
                let y1 = cy + dy1;
                cx += dx;
                cy += dy;
                cmds.push(PathCommand::QuadTo {
                    x1,
                    y1,
                    x: cx,
                    y: cy,
                });
                last_cmd = b'q';
            }
            b'Z' | b'z' => {
                cmds.push(PathCommand::Close);
                // Per SVG spec: after closepath, current point returns to subpath start.
                cx = subpath_start.0;
                cy = subpath_start.1;
                last_cmd = b'M';
            }
            _ => {
                i += 1;
            }
        }
    }
    cmds
}

#[derive(Debug)]
enum Token {
    Cmd(u8),
    Num(f64),
}

fn tokenize_svg(d: &str) -> Vec<Token> {
    let mut tokens = Vec::new();
    let bytes = d.as_bytes();
    let mut i = 0;
    while i < bytes.len() {
        let b = bytes[i];
        if b.is_ascii_alphabetic() {
            tokens.push(Token::Cmd(b));
            i += 1;
        } else if b == b'-' || b == b'.' || b.is_ascii_digit() {
            let start = i;
            if b == b'-' {
                i += 1;
            }
            let mut has_dot = false;
            while i < bytes.len() && (bytes[i].is_ascii_digit() || (bytes[i] == b'.' && !has_dot)) {
                if bytes[i] == b'.' {
                    has_dot = true;
                }
                i += 1;
            }
            if let Ok(n) = std::str::from_utf8(&bytes[start..i])
                .unwrap_or("0")
                .parse::<f64>()
            {
                tokens.push(Token::Num(n));
            }
        } else {
            i += 1;
        }
    }
    tokens
}

fn read1(tokens: &[Token], i: &mut usize) -> f64 {
    if *i < tokens.len() {
        if let Token::Num(n) = tokens[*i] {
            *i += 1;
            return n;
        }
    }
    0.0
}

fn read2(tokens: &[Token], i: &mut usize) -> (f64, f64) {
    let a = read1(tokens, i);
    let b = read1(tokens, i);
    (a, b)
}

// ---------------------------------------------------------------------------
// KaTeX raw SVG path data (from svgGeometry.js)
// ---------------------------------------------------------------------------

// twoheadleftarrow from glyph U+219E in font KaTeX AMS Regular (viewBox 400000×334)
const TWOHEADLEFTARROW: &str = "M0 167c68 40 115.7 95.7 143 167h22c15.3 0 23-.3 23-1 0-1.3-5.3-13.7-16-37-18-35.3-41.3-69-70-101l-7-8h125l9 7c50.7 39.3 85 86 103 140h46c0-4.7-6.3-18.7-19-42-18-35.3-40-67.3-66-96l-9-9h399716v-40H284l9-9c26-28.7 48-60.7 66-96 12.7-23.333 19-37.333 19-42h-46c-18 54-52.3 100.7-103 140l-9 7H95l7-8c28.7-32 52-65.7 70-101 10.7-23.333 16-35.7 16-37 0-.7-7.7-1-23-1h-22C115.7 71.3 68 127 0 167z";

// rightarrow from glyph U+2192 in font KaTeX Main (viewBox 400000×534, scale 1000:1)
const RIGHTARROW: &str = "M0 241v40h399891c-47.3 35.3-84 78-110 128 -16.7 32-27.7 63.7-33 95 0 1.3-.2 2.7-.5 4-.3 1.3-.5 2.3-.5 3 0 7.3 6.7 11 20 11 8 0 13.2-.8 15.5-2.5 2.3-1.7 4.2-5.5 5.5-11.5 2-13.3 5.7-27 11-41 14.7-44.7 39-84.5 73-119.5s73.7-60.2 119-75.5c6-2 9-5.7 9-11s-3-9-9-11c-45.3-15.3-85 -40.5-119-75.5s-58.3-74.8-73-119.5c-4.7-14-8.3-27.3-11-40-1.3-6.7-3.2-10.8-5.5 -12.5-2.3-1.7-7.5-2.5-15.5-2.5-14 0-21 3.7-21 11 0 2 2 10.3 6 25 20.7 83.3 67 151.7 139 205zm0 0v40h399900v-40z";

// leftarrow from glyph U+2190 in font KaTeX Main (viewBox 400000×534)
const LEFTARROW: &str = "M400000 241H110l3-3c68.7-52.7 113.7-120 135-202 4-14.7 6-23 6-25 0-7.3-7-11-21-11-8 0-13.2.8-15.5 2.5-2.3 1.7-4.2 5.8 -5.5 12.5-1.3 4.7-2.7 10.3-4 17-12 48.7-34.8 92-68.5 130S65.3 228.3 18 247 c-10 4-16 7.7-18 11 0 8.7 6 14.3 18 17 47.3 18.7 87.8 47 121.5 85S196 441.3 208 490c.7 2 1.3 5 2 9s1.2 6.7 1.5 8c.3 1.3 1 3.3 2 6s2.2 4.5 3.5 5.5c1.3 1 3.3 1.8 6 2.5s6 1 10 1c14 0 21-3.7 21-11 0-2-2-10.3-6-25-20-79.3-65-146.7-135-202 l-3-3h399890zM100 241v40h399900v-40z";

// leftmapsto: vertical bar at left (x=0..40) + shaft, for \xmapsto (viewBox 400000×534)
const LEFTMAPSTO: &str = "M40 281 V448H0V74H40V241H400000v40z";

// twoheadrightarrow from glyph U+21A0 in font KaTeX AMS Regular (viewBox 400000×334)
const TWOHEADRIGHTARROW: &str = "M400000 167c-68-40-115.7-95.7-143-167h-22c-15.3 0-23 .3-23 1 0 1.3 5.3 13.7 16 37 18 35.3 41.3 69 70 101l7 8h-125l-9-7c-50.7-39.3-85-86-103-140h-46c0 4.7 6.3 18.7 19 42 18 35.3 40 67.3 66 96l9 9H0v40h399716l-9 9c-26 28.7-48 60.7-66 96-12.7 23.333-19 37.333-19 42h46c18-54 52.3-100.7 103-140l9-7h125l-7 8c-28.7 32-52 65.7-70 101-10.7 23.333-16 35.7-16 37 0 .7 7.7 1 23 1h22c27.3-71.3 75-127 143-167z";

const WIDEHAT: [&str; 4] = [
    // widehat1
    "M529 0h5l519 115c5 1 9 5 9 10 0 1-1 2-1 3l-4 22\
c-1 5-5 9-11 9h-2L532 67 19 159h-2c-5 0-9-4-11-9l-5-22c-1-6 2-12 8-13z",
    // widehat2
    "M1181 0h2l1171 176c6 0 10 5 10 11l-2 23c-1 6-5 10\
-11 10h-1L1182 67 15 220h-1c-6 0-10-4-11-10l-2-23c-1-6 4-11 10-11z",
    // widehat3
    "M1181 0h2l1171 236c6 0 10 5 10 11l-2 23c-1 6-5 10\
-11 10h-1L1182 67 15 280h-1c-6 0-10-4-11-10l-2-23c-1-6 4-11 10-11z",
    // widehat4
    "M1181 0h2l1171 296c6 0 10 5 10 11l-2 23c-1 6-5 10\
-11 10h-1L1182 67 15 340h-1c-6 0-10-4-11-10l-2-23c-1-6 4-11 10-11z",
];

const WIDECHECK: [&str; 4] = [
    "M529,159h5l519,-115c5,-1,9,-5,9,-10c0,-1,-1,-2,-1,-3l-4,-22c-1,\
-5,-5,-9,-11,-9h-2l-512,92l-513,-92h-2c-5,0,-9,4,-11,9l-5,22c-1,6,2,12,8,13z",
    "M1181,220h2l1171,-176c6,0,10,-5,10,-11l-2,-23c-1,-6,-5,-10,\
-11,-10h-1l-1168,153l-1167,-153h-1c-6,0,-10,4,-11,10l-2,23c-1,6,4,11,10,11z",
    "M1181,280h2l1171,-236c6,0,10,-5,10,-11l-2,-23c-1,-6,-5,-10,\
-11,-10h-1l-1168,213l-1167,-213h-1c-6,0,-10,4,-11,10l-2,23c-1,6,4,11,10,11z",
    "M1181,340h2l1171,-296c6,0,10,-5,10,-11l-2,-23c-1,-6,-5,-10,\
-11,-10h-1l-1168,273l-1167,-273h-1c-6,0,-10,4,-11,10l-2,23c-1,6,4,11,10,11z",
];

const TILDE: [&str; 4] = [
    "M200 55.538c-77 0-168 73.953-177 73.953-3 0-7
-2.175-9-5.437L2 97c-1-2-2-4-2-6 0-4 2-7 5-9l20-12C116 12 171 0 207 0c86 0
 114 68 191 68 78 0 168-68 177-68 4 0 7 2 9 5l12 19c1 2.175 2 4.35 2 6.525 0
 4.35-2 7.613-5 9.788l-19 13.05c-92 63.077-116.937 75.308-183 76.128
-68.267.847-113-73.952-191-73.952z",
    "M344 55.266c-142 0-300.638 81.316-311.5 86.418
-8.01 3.762-22.5 10.91-23.5 5.562L1 120c-1-2-1-3-1-4 0-5 3-9 8-10l18.4-9C160.9
 31.9 283 0 358 0c148 0 188 122 331 122s314-97 326-97c4 0 8 2 10 7l7 21.114
c1 2.14 1 3.21 1 4.28 0 5.347-3 9.626-7 10.696l-22.3 12.622C852.6 158.372 751
 181.476 676 181.476c-149 0-189-126.21-332-126.21z",
    "M786 59C457 59 32 175.242 13 175.242c-6 0-10-3.457
-11-10.37L.15 138c-1-7 3-12 10-13l19.2-6.4C378.4 40.7 634.3 0 804.3 0c337 0
 411.8 157 746.8 157 328 0 754-112 773-112 5 0 10 3 11 9l1 14.075c1 8.066-.697
 16.595-6.697 17.492l-21.052 7.31c-367.9 98.146-609.15 122.696-778.15 122.696
 -338 0-409-156.573-744-156.573z",
    "M786 58C457 58 32 177.487 13 177.487c-6 0-10-3.345
-11-10.035L.15 143c-1-7 3-12 10-13l22-6.7C381.2 35 637.15 0 807.15 0c337 0 409
 177 744 177 328 0 754-127 773-127 5 0 10 3 11 9l1 14.794c1 7.805-3 13.38-9
 14.495l-20.7 5.574c-366.85 99.79-607.3 139.372-776.3 139.372-338 0-409
 -175.236-744-175.236z",
];

// doubleleftarrow from glyph U+21D0 in font KaTeX Main (viewBox 400000×560)
const DOUBLELEFTARROW: &str = "M262 157 l10-10c34-36 62.7-77 86-123 3.3-8 5-13.3 5-16 0-5.3-6.7-8-20-8-7.3 0-12.2.5-14.5 1.5-2.3 1-4.8 4.5-7.5 10.5-49.3 97.3-121.7 169.3-217 216-28 14-57.3 25-88 33-6.7 2-11 3.8-13 5.5-2 1.7-3 4.2-3 7.5s1 5.8 3 7.5 c2 1.7 6.3 3.5 13 5.5 68 17.3 128.2 47.8 180.5 91.5 52.3 43.7 93.8 96.2 124.5 157.5 9.3 8 15.3 12.3 18 13h6c12-.7 18-4 18-10 0-2-1.7-7-5-15-23.3-46-52-87-86-123l-10-10h399738v-40H218c328 0 0 0 0 0l-10-8c-26.7-20-65.7-43-117-69 2.7-2 6-3.7 10-5 36.7-16 72.3-37.3 107-64l10-8h399782v-40z m8 0v40h399730v-40zm0 194v40h399730v-40z";

// doublerightarrow from glyph U+21D2 in font KaTeX Main (viewBox 400000×560)
const DOUBLERIGHTARROW: &str = "M399738 392l -10 10c-34 36-62.7 77-86 123-3.3 8-5 13.3-5 16 0 5.3 6.7 8 20 8 7.3 0 12.2-.5 14.5-1.5 2.3-1 4.8-4.5 7.5-10.5 49.3-97.3 121.7-169.3 217-216 28-14 57.3-25 88-33 6.7-2 11-3.8 13-5.5 2-1.7 3-4.2 3-7.5s-1-5.8-3-7.5c-2-1.7-6.3-3.5-13-5.5-68-17.3-128.2-47.8-180.5-91.5-52.3-43.7-93.8-96.2-124.5-157.5-9.3-8-15.3-12.3-18-13h-6c-12 .7-18 4-18 10 0 2 1.7 7 5 15 23.3 46 52 87 86 123l10 10H0v40h399782 c-328 0 0 0 0 0l10 8c26.7 20 65.7 43 117 69-2.7 2-6 3.7-10 5-36.7 16-72.3 37.3-107 64l-10 8H0v40zM0 157v40h399730v-40zm0 194v40h399730v-40z";

// leftharpoon from glyph U+21BD in font KaTeX Main (viewBox 400000×522)
const LEFTHARPOON: &str = "M0 267c.7 5.3 3 10 7 14h399993v-40H93c3.3-3.3 10.2-9.5 20.5-18.5s17.8-15.8 22.5-20.5c50.7-52 88-110.3 112-175 4-11.3 5-18.3 3-21-1.3-4-7.3-6-18-6-8 0-13 .7-15 2s-4.7 6.7-8 16c-42 98.7-107.3 174.7-196 228-6.7 4.7-10.7 8-12 10-1.3 2-2 5.7-2 11zm100-26v40h399900v-40z";

// leftharpoonplus (viewBox 400000×716)
const LEFTHARPOONPLUS: &str = "M0 267c.7 5.3 3 10 7 14h399993v-40H93c3.3-3.3 10.2-9.5 20.5-18.5s17.8-15.8 22.5-20.5c50.7-52 88-110.3 112-175 4-11.3 5-18.3 3-21-1.3-4-7.3-6-18-6-8 0-13 .7-15 2s-4.7 6.7-8 16c-42 98.7-107.3 174.7-196 228-6.7 4.7-10.7 8-12 10-1.3 2-2 5.7-2 11zm100-26v40h399900v-40zM0 435v40h400000v-40z m0 0v40h400000v-40z";

// leftharpoondown (viewBox 400000×522)
const LEFTHARPOONDOWN: &str = "M7 241c-4 4-6.333 8.667-7 14 0 5.333.667 9 2 11s5.333 5.333 12 10c90.667 54 156 130 196 228 3.333 10.667 6.333 16.333 9 17 2 .667 5 1 9 1h5c10.667 0 16.667-2 18-6 2-2.667 1-9.667-3-21-32-87.333-82.667-157.667-152-211l-3-3h399907v-40zM93 281 H400000 v-40L7 241z";

// leftharpoondownplus (viewBox 400000×716)
const LEFTHARPOONDOWNPLUS: &str = "M7 435c-4 4-6.3 8.7-7 14 0 5.3.7 9 2 11s5.3 5.3 12 10c90.7 54 156 130 196 228 3.3 10.7 6.3 16.3 9 17 2 .7 5 1 9 1h5c10.7 0 16.7-2 18-6 2-2.7 1-9.7-3-21-32-87.3-82.7-157.7-152-211l-3-3h399907v-40H7zm93 0 v40h399900v-40zM0 241v40h399900v-40zm0 0v40h399900v-40z";

// rightharpoon from glyph U+21C0 in font KaTeX Main (viewBox 400000×522)
const RIGHTHARPOON: &str = "M0 241v40h399993c4.7-4.7 7-9.3 7-14 0-9.3-3.7-15.3-11-18-92.7-56.7-159-133.7-199-231-3.3-9.3-6-14.7-8-16-2-1.3-7-2-15-2-10.7 0-16.7 2-18 6-2 2.7-1 9.7 3 21 15.3 42 36.7 81.8 64 119.5 27.3 37.7 58 69.2 92 94.5zm0 0v40h399900v-40z";

// rightharpoonplus (viewBox 400000×716)
const RIGHTHARPOONPLUS: &str = "M0 241v40h399993c4.7-4.7 7-9.3 7-14 0-9.3-3.7-15.3-11-18-92.7-56.7-159-133.7-199-231-3.3-9.3-6-14.7-8-16-2-1.3-7-2-15-2-10.7 0-16.7 2-18 6-2 2.7-1 9.7 3 21 15.3 42 36.7 81.8 64 119.5 27.3 37.7 58 69.2 92 94.5z m0 0v40h399900v-40z m100 194v40h399900v-40zm0 0v40h399900v-40z";

// rightharpoondown (viewBox 400000×522)
const RIGHTHARPOONDOWN: &str = "M399747 511c0 7.3 6.7 11 20 11 8 0 13-.8 15-2.5s4.7-6.8 8-15.5c40-94 99.3-166.3 178-217 13.3-8 20.3-12.3 21-13 5.3-3.3 8.5-5.8 9.5-7.5 1-1.7 1.5-5.2 1.5-10.5s-2.3-10.3-7-15H0v40h399908c-34 25.3-64.7 57-92 95-27.3 38-48.7 77.7-64 119-3.3 8.7-5 14-5 16zM0 241v40h399900v-40z";

// rightharpoondownplus (viewBox 400000×716)
const RIGHTHARPOONDOWNPLUS: &str = "M399747 705c0 7.3 6.7 11 20 11 8 0 13-.8 15-2.5s4.7-6.8 8-15.5c40-94 99.3-166.3 178-217 13.3-8 20.3-12.3 21-13 5.3-3.3 8.5-5.8 9.5-7.5 1-1.7 1.5-5.2 1.5-10.5s-2.3-10.3-7-15H0v40h399908c-34 25.3-64.7 57-92 95-27.3 38-48.7 77.7-64 119-3.3 8.7-5 14-5 16zM0 435v40h399900v-40z m0-194v40h400000v-40zm0 0v40h400000v-40z";

// mhchem \\xrightequilibrium / \\xleftequilibrium (KaTeX katexImagesData, viewBox 400000×716)
const BARABOVESHORTLEFTHARPOON: &str = "M507,435c-4,4,-6.3,8.7,-7,14c0,5.3,0.7,9,2,11c1.3,2,5.3,5.3,12,10c90.7,54,156,130,196,228c3.3,10.7,6.3,16.3,9,17c2,0.7,5,1,9,1c0,0,5,0,5,0c10.7,0,16.7,-2,18,-6c2,-2.7,1,-9.7,-3,-21c-32,-87.3,-82.7,-157.7,-152,-211c0,0,-3,-3,-3,-3l399351,0l0,-40c-398570,0,-399437,0,-399437,0z M593 435 v40 H399500 v-40zM0 281 v-40 H399908 v40z M0 281 v-40 H399908 v40z";
const RIGHTHARPOONABOVESHORTBAR: &str = "M0,241 l0,40c399126,0,399993,0,399993,0c4.7,-4.7,7,-9.3,7,-14c0,-9.3,-3.7,-15.3,-11,-18c-92.7,-56.7,-159,-133.7,-199,-231c-3.3,-9.3,-6,-14.7,-8,-16c-2,-1.3,-7,-2,-15,-2c-10.7,0,-16.7,2,-18,6c-2,2.7,-1,9.7,3,21c15.3,42,36.7,81.8,64,119.5c27.3,37.7,58,69.2,92,94.5zM0 241 v40 H399908 v-40z M0 475 v-40 H399500 v40z M0 475 v-40 H399500 v40z";
const SHORTBARABOVELEFTHARPOON: &str = "M7,435c-4,4,-6.3,8.7,-7,14c0,5.3,0.7,9,2,11c1.3,2,5.3,5.3,12,10c90.7,54,156,130,196,228c3.3,10.7,6.3,16.3,9,17c2,0.7,5,1,9,1c0,0,5,0,5,0c10.7,0,16.7,-2,18,-6c2,-2.7,1,-9.7,-3,-21c-32,-87.3,-82.7,-157.7,-152,-211c0,0,-3,-3,-3,-3l399907,0l0,-40c-399126,0,-399993,0,-399993,0zM93 435 v40 H400000 v-40z M500 241 v40 H400000 v-40z M500 241 v40 H400000 v-40z";
const SHORTRIGHTHARPOONABOVEBAR: &str = "M53,241l0,40c398570,0,399437,0,399437,0c4.7,-4.7,7,-9.3,7,-14c0,-9.3,-3.7,-15.3,-11,-18c-92.7,-56.7,-159,-133.7,-199,-231c-3.3,-9.3,-6,-14.7,-8,-16c-2,-1.3,-7,-2,-15,-2c-10.7,0,-16.7,2,-18,6c-2,2.7,-1,9.7,3,21c15.3,42,36.7,81.8,64,119.5c27.3,37.7,58,69.2,92,94.5zM500 241 v40 H399408 v-40z M500 435 v40 H400000 v-40z";

// lefthook from glyph U+21A9 in font KaTeX Main (viewBox 400000×522)
const LEFTHOOK: &str = "M400000 281 H103s-33-11.2-61-33.5S0 197.3 0 164s14.2-61.2 42.5-83.5C70.8 58.2 104 47 142 47 c16.7 0 25 6.7 25 20 0 12-8.7 18.7-26 20-40 3.3-68.7 15.7-86 37-10 12-15 25.3-15 40 0 22.7 9.8 40.7 29.5 54 19.7 13.3 43.5 21 71.5 23h399859zM103 281v-40h399897v40z";

// righthook (viewBox 400000×522)
const RIGHTHOOK: &str = "M399859 241c-764 0 0 0 0 0 40-3.3 68.7-15.7 86-37 10-12 15-25.3 15-40 0-22.7-9.8-40.7-29.5-54-19.7-13.3-43.5-21-71.5-23-17.3-1.3-26-8-26-20 0-13.3 8.7-20 26-20 38 0 71 11.2 99 33.5 0 0 7 5.6 21 16.7 14 11.2 21 33.5 21 66.8s-14 61.2-42 83.5c-28 22.3-61 33.5-99 33.5L0 241z M0 281v-40h399859v40z";

// leftbrace from glyphs U+23A9/23A8/23A7 in font KaTeX_Size4-Regular (viewBox 400000×548)
const LEFTBRACE: &str = "M6 548l-6-6v-35l6-11c56-104 135.3-181.3 238-232 57.3-28.7 117-45 179-50h399577v120H403c-43.3 7-81 15-113 26-100.7 33-179.7 91-237 174-2.7 5-6 9-10 13-.7 1-7.3 1-20 1H6z";

// midbrace (viewBox 400000×548)
const MIDBRACE: &str = "M200428 334 c-100.7-8.3-195.3-44-280-108-55.3-42-101.7-93-139-153l-9-14c-2.7 4-5.7 8.7-9 14-53.3 86.7-123.7 153-211 199-66.7 36-137.3 56.3-212 62H0V214h199568c178.3-11.7 311.7-78.3 403-201 6-8 9.7-12 11-12 .7-.7 6.7-1 18-1s17.3.3 18 1c1.3 0 5 4 11 12 44.7 59.3 101.3 106.3 170 141s145.3 54.3 229 60h199572v120z";

// rightbrace (viewBox 400000×548)
const RIGHTBRACE: &str = "M400000 542l -6 6h-17c-12.7 0-19.3-.3-20-1-4-4-7.3-8.3-10-13-35.3-51.3-80.8-93.8-136.5-127.5s-117.2-55.8-184.5-66.5c-.7 0-2-.3-4-1-18.7-2.7-76-4.3-172-5H0V214h399571l6 1 c124.7 8 235 61.7 331 161 31.3 33.3 59.7 72.7 85 118l7 13v35z";

// leftbraceunder (viewBox 400000×548)
const LEFTBRACEUNDER: &str = "M0 6l6-6h17c12.688 0 19.313.3 20 1 4 4 7.313 8.3 10 13 35.313 51.3 80.813 93.8 136.5 127.5 55.688 33.7 117.188 55.8 184.5 66.5.688 0 2 .3 4 1 18.688 2.7 76 4.3 172 5h399450v120H429l-6-1c-124.688-8-235-61.7-331-161C60.687 138.7 32.312 99.3 7 54L0 41V6z";

// midbraceunder (viewBox 400000×548)
const MIDBRACEUNDER: &str = "M199572 214 c100.7 8.3 195.3 44 280 108 55.3 42 101.7 93 139 153l9 14c2.7-4 5.7-8.7 9-14 53.3-86.7 123.7-153 211-199 66.7-36 137.3-56.3 212-62h199568v120H200432c-178.3 11.7-311.7 78.3-403 201-6 8-9.7 12-11 12-.7.7-6.7 1-18 1s-17.3-.3-18-1c-1.3 0-5-4-11-12-44.7-59.3-101.3-106.3-170-141s-145.3-54.3-229-60H0V214z";

// rightbraceunder (viewBox 400000×548)
const RIGHTBRACEUNDER: &str = "M399994 0l6 6v35l-6 11c-56 104-135.3 181.3-238 232-57.3 28.7-117 45-179 50H-300V214h399897c43.3-7 81-15 113-26 100.7-33 179.7-91 237-174 2.7-5 6-9 10-13 .7-1 7.3-1 20-1h17z";

// leftToFrom from glyph U+21C4 in font KaTeX AMS Regular (viewBox 400000×528)
const LEFTTOFROM: &str = "M0 147h400000v40H0zm0 214c68 40 115.7 95.7 143 167h22c15.3 0 23-.3 23-1 0-1.3-5.3-13.7-16-37-18-35.3-41.3-69-70-101l-7-8h399905v-40H95l7-8 c28.7-32 52-65.7 70-101 10.7-23.3 16-35.7 16-37 0-.7-7.7-1-23-1h-22C115.7 265.3 68 321 0 361zm0-174v-40h399900v40zm100 154v40h399900v-40z";

// rightToFrom (viewBox 400000×528)
const RIGHTTOFROM: &str = "M400000 167c-70.7-42-118-97.7-142-167h-23c-15.3 0-23 .3-23 1 0 1.3 5.3 13.7 16 37 18 35.3 41.3 69 70 101l7 8H0v40h399905l-7 8c-28.7 32-52 65.7-70 101-10.7 23.3-16 35.7-16 37 0 .7 7.7 1 23 1h23c24-69.3 71.3-125 142-167z M100 147v40h399900v-40zM0 341v40h399900v-40z";

// baraboveleftarrow from KaTeX mhchem (viewBox 400000×620)
const BARABOVELEFTARROW: &str = "M400000 620h-399890l3-3c68.7-52.7 113.7-120 135-202 c4-14.7 6-23 6-25c0-7.3-7-11-21-11c-8 0-13.2 0.8-15.5 2.5c-2.3 1.7-4.2 5.8-5.5 12.5c-1.3 4.7-2.7 10.3-4 17c-12 48.7-34.8 92-68.5 130 s-74.2 66.3-121.5 85c-10 4-16 7.7-18 11c0 8.7 6 14.3 18 17c47.3 18.7 87.8 47 121.5 85s56.5 81.3 68.5 130c0.7 2 1.3 5 2 9s1.2 6.7 1.5 8c0.3 1.3 1 3.3 2 6 s2.2 4.5 3.5 5.5c1.3 1 3.3 1.8 6 2.5s6 1 10 1c14 0 21-3.7 21-11 c0-2-2-10.3-6-25c-20-79.3-65-146.7-135-202l-3-3h399890z M100 620v40h399900v-40z M0 241v40h399900v-40zM0 241v40h399900v-40z";

// rightarrowabovebar from KaTeX mhchem (viewBox 400000×620)
const RIGHTARROWABOVEBAR: &str = "M0 241v40h399891c-47.3 35.3-84 78-110 128-16.7 32-27.7 63.7-33 95 0 1.3-.2 2.7-.5 4-.3 1.3-.5 2.3-.5 3 0 7.3 6.7 11 20 11 8 0 13.2-.8 15.5-2.5 2.3-1.7 4.2-5.5 5.5-11.5 2-13.3 5.7-27 11-41 14.7-44.7 39-84.5 73-119.5s73.7-60.2 119-75.5c6-2 9-5.7 9-11s-3-9-9-11c-45.3-15.3-85-40.5-119-75.5s-58.3-74.8-73-119.5c-4.7-14-8.3-27.3-11-40-1.3-6.7-3.2-10.8-5.5-12.5-2.3-1.7-7.5-2.5-15.5-2.5-14 0-21 3.7-21 11 0 2 2 10.3 6 25 20.7 83.3 67 151.7 139 205zm96 379h399894v40H0zm0 0h399904v40H0z";

// longequal (viewBox 400000×334)
const LONGEQUAL: &str =
    "M0 50 h400000 v40H0z m0 194h400000v40H0z M0 50 h400000 v40H0z m0 194h400000v40H0z";

// leftlinesegment (viewBox 400000×522)
const LEFTLINESEGMENT: &str =
    "M40 281 V428 H0 V94 H40 V241 H400000 v40z M40 281 V428 H0 V94 H40 V241 H400000 v40z";

// rightlinesegment (viewBox 400000×522)
const RIGHTLINESEGMENT: &str =
    "M399960 241 V94 h40 V428 h-40 V281 H0 v-40z M399960 241 V94 h40 V428 h-40 V281 H0 v-40z";

// leftgroup (for \overgroup, viewBox 400000×342)
const LEFTGROUP: &str = "M400000 80 H435C64 80 168.3 229.4 21 260c-5.9 1.2-18 0-18 0-2 0-3-1-3-3v-38C76 61 257 0 435 0h399565z";

// leftgroupunder (for \undergroup, viewBox 400000×342)
const LEFTGROUPUNDER: &str = "M400000 262 H435C64 262 168.3 112.6 21 82c-5.9-1.2-18 0-18 0-2 0-3 1-3 3v38c76 158 257 219 435 219h399565z";

// rightgroup (for \overgroup, viewBox 400000×342)
const RIGHTGROUP: &str = "M0 80h399565c371 0 266.7 149.4 414 180 5.9 1.2 18 0 18 0 2 0 3-1 3-3v-38c-76-158-257-219-435-219H0z";

// rightgroupunder (for \undergroup, viewBox 400000×342)
const RIGHTGROUPUNDER: &str = "M0 262h399565c371 0 266.7-149.4 414-180 5.9-1.2 18 0 18 0 2 0 3 1 3 3v38c-76 158-257 219-435 219H0z";

// Horizontal brackets (KaTeX `svgGeometry.ts`, viewBox 400000×440 / 410)
const LEFTBRACKETOVER: &str = "M0 440 h120 V150 H399995 v-120 H0z";
const RIGHTBRACKETOVER: &str = "M399995 440 h-120 V150 H0 v-120 H399995z";
const LEFTBRACKETUNDER: &str = "M0 0 h120 V290 H399995 v120 H0z";
const RIGHTBRACKETUNDER: &str = "M399995 0 h-120 V290 H0 v120 H400000z";

// vec from glyph U+20D7 in font KaTeX Main
const _VEC_KATEX: &str = "M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5 3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11 10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59 H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359 c-16-25.333-24-45-24-59z";

// ---------------------------------------------------------------------------
// KaTeX images data lookup table (from katexImagesData in katex.js)
// ---------------------------------------------------------------------------

struct KatexImageData {
    paths: &'static [&'static str],
    /// KaTeX `katexImagesData` minimum arrow shaft width (em).
    min_width: f64,
    vb_height: f64,
    align: Option<&'static str>,
}

fn katex_image_data(label: &str) -> Option<KatexImageData> {
    let key = label.trim_start_matches('\\');
    match key {
        "overrightarrow" => Some(KatexImageData {
            paths: &["rightarrow"],
            min_width: 0.888,
            vb_height: 522.0,
            align: Some("xMaxYMin"),
        }),
        "overleftarrow" => Some(KatexImageData {
            paths: &["leftarrow"],
            min_width: 0.888,
            vb_height: 522.0,
            align: Some("xMinYMin"),
        }),
        "underrightarrow" => Some(KatexImageData {
            paths: &["rightarrow"],
            min_width: 0.888,
            vb_height: 522.0,
            align: Some("xMaxYMin"),
        }),
        "underleftarrow" => Some(KatexImageData {
            paths: &["leftarrow"],
            min_width: 0.888,
            vb_height: 522.0,
            align: Some("xMinYMin"),
        }),
        "xrightarrow" => Some(KatexImageData {
            paths: &["rightarrow"],
            min_width: 1.469,
            vb_height: 522.0,
            align: Some("xMaxYMin"),
        }),
        // KaTeX `stretchy.js` `katexImagesData`: `\\cdrightarrow` / `\\cdlongequal` use minWidth 3.0em
        // (comment “CD minwwidth2.5pc” — amscd `\minCDarrowwidth`, ~2.5pc ≈ 3em at 10pt).
        "cdrightarrow" => Some(KatexImageData {
            paths: &["rightarrow"],
            min_width: 3.0,
            vb_height: 522.0,
            align: Some("xMaxYMin"),
        }),
        "cdleftarrow" => Some(KatexImageData {
            paths: &["leftarrow"],
            min_width: 3.0,
            vb_height: 522.0,
            align: Some("xMinYMin"),
        }),
        "xleftarrow" => Some(KatexImageData {
            paths: &["leftarrow"],
            min_width: 1.469,
            vb_height: 522.0,
            align: Some("xMinYMin"),
        }),
        "Overrightarrow" => Some(KatexImageData {
            paths: &["doublerightarrow"],
            min_width: 0.888,
            vb_height: 560.0,
            align: Some("xMaxYMin"),
        }),
        "xRightarrow" => Some(KatexImageData {
            paths: &["doublerightarrow"],
            min_width: 1.526,
            vb_height: 560.0,
            align: Some("xMaxYMin"),
        }),
        "xLeftarrow" => Some(KatexImageData {
            paths: &["doubleleftarrow"],
            min_width: 1.526,
            vb_height: 560.0,
            align: Some("xMinYMin"),
        }),
        "overleftharpoon" | "xleftharpoonup" => Some(KatexImageData {
            paths: &["leftharpoon"],
            min_width: 0.888,
            vb_height: 522.0,
            align: Some("xMinYMin"),
        }),
        "xleftharpoondown" => Some(KatexImageData {
            paths: &["leftharpoondown"],
            min_width: 0.888,
            vb_height: 522.0,
            align: Some("xMinYMin"),
        }),
        "overrightharpoon" | "xrightharpoonup" => Some(KatexImageData {
            paths: &["rightharpoon"],
            min_width: 0.888,
            vb_height: 522.0,
            align: Some("xMaxYMin"),
        }),
        "xrightharpoondown" => Some(KatexImageData {
            paths: &["rightharpoondown"],
            min_width: 0.888,
            vb_height: 522.0,
            align: Some("xMaxYMin"),
        }),
        "xlongequal" => Some(KatexImageData {
            paths: &["longequal"],
            min_width: 0.888,
            vb_height: 334.0,
            align: Some("xMinYMin"),
        }),
        "cdlongequal" => Some(KatexImageData {
            paths: &["longequal"],
            min_width: 3.0,
            vb_height: 334.0,
            align: Some("xMinYMin"),
        }),
        "xtwoheadleftarrow" => Some(KatexImageData {
            paths: &["twoheadleftarrow"],
            min_width: 0.888,
            vb_height: 334.0,
            align: Some("xMinYMin"),
        }),
        "xtwoheadrightarrow" => Some(KatexImageData {
            paths: &["twoheadrightarrow"],
            min_width: 0.888,
            vb_height: 334.0,
            align: Some("xMaxYMin"),
        }),
        "overleftrightarrow" => Some(KatexImageData {
            paths: &["leftarrow", "rightarrow"],
            min_width: 0.888,
            vb_height: 522.0,
            align: None,
        }),
        "underleftrightarrow" => Some(KatexImageData {
            paths: &["leftarrow", "rightarrow"],
            min_width: 0.888,
            vb_height: 522.0,
            align: None,
        }),
        "xleftrightarrow" => Some(KatexImageData {
            paths: &["leftarrow", "rightarrow"],
            min_width: 1.75,
            vb_height: 522.0,
            align: None,
        }),
        "xLeftrightarrow" => Some(KatexImageData {
            paths: &["doubleleftarrow", "doublerightarrow"],
            min_width: 1.75,
            vb_height: 560.0,
            align: None,
        }),
        "xrightleftharpoons" => Some(KatexImageData {
            paths: &["leftharpoondownplus", "rightharpoonplus"],
            min_width: 1.75,
            vb_height: 716.0,
            align: None,
        }),
        "xleftrightharpoons" => Some(KatexImageData {
            paths: &["leftharpoonplus", "rightharpoondownplus"],
            min_width: 1.75,
            vb_height: 716.0,
            align: None,
        }),
        "xrightequilibrium" => Some(KatexImageData {
            paths: &["baraboveshortleftharpoon", "rightharpoonaboveshortbar"],
            min_width: 1.75,
            vb_height: 716.0,
            align: None,
        }),
        "xleftequilibrium" => Some(KatexImageData {
            paths: &["shortbaraboveleftharpoon", "shortrightharpoonabovebar"],
            min_width: 1.75,
            vb_height: 716.0,
            align: None,
        }),
        "xhookleftarrow" => Some(KatexImageData {
            paths: &["leftarrow", "righthook"],
            min_width: 1.08,
            vb_height: 522.0,
            align: None,
        }),
        "xhookrightarrow" => Some(KatexImageData {
            paths: &["lefthook", "rightarrow"],
            min_width: 1.08,
            vb_height: 522.0,
            align: None,
        }),
        "overlinesegment" => Some(KatexImageData {
            paths: &["leftlinesegment", "rightlinesegment"],
            min_width: 0.888,
            vb_height: 522.0,
            align: None,
        }),
        "underlinesegment" => Some(KatexImageData {
            paths: &["leftlinesegment", "rightlinesegment"],
            min_width: 0.888,
            vb_height: 522.0,
            align: None,
        }),
        "overgroup" => Some(KatexImageData {
            paths: &["leftgroup", "rightgroup"],
            min_width: 0.888,
            vb_height: 342.0,
            align: None,
        }),
        "undergroup" => Some(KatexImageData {
            paths: &["leftgroupunder", "rightgroupunder"],
            min_width: 0.888,
            vb_height: 342.0,
            align: None,
        }),
        "xmapsto" => Some(KatexImageData {
            paths: &["leftmapsto", "rightarrow"],
            min_width: 1.5,
            vb_height: 522.0,
            align: None,
        }),
        "xtofrom" => Some(KatexImageData {
            paths: &["leftToFrom", "rightToFrom"],
            min_width: 1.75,
            vb_height: 528.0,
            align: None,
        }),
        "xrightleftarrows" => Some(KatexImageData {
            paths: &["baraboveleftarrow", "rightarrowabovebar"],
            min_width: 1.75,
            vb_height: 901.0,
            align: None,
        }),
        // Overbrace/underbrace: KaTeX Size4 glyphs (viewBox 400000×548), same 3-piece horizontal joining as stretchy arrows.
        "overbrace" => Some(KatexImageData {
            paths: &["leftbrace", "midbrace", "rightbrace"],
            min_width: 0.888,
            vb_height: 548.0,
            align: None,
        }),
        "underbrace" => Some(KatexImageData {
            paths: &["leftbraceunder", "midbraceunder", "rightbraceunder"],
            min_width: 0.888,
            vb_height: 548.0,
            align: None,
        }),
        // mathtools `\overbracket` / `\underbracket`: 2-piece KaTeX SVG (same minWidth/height as KaTeX `stretchy.ts`).
        "overbracket" => Some(KatexImageData {
            paths: &["leftbracketover", "rightbracketover"],
            min_width: 1.6,
            vb_height: 440.0,
            align: None,
        }),
        "underbracket" => Some(KatexImageData {
            paths: &["leftbracketunder", "rightbracketunder"],
            min_width: 1.6,
            vb_height: 410.0,
            align: None,
        }),
        _ => None,
    }
}

/// KaTeX [`katexImagesData`](https://github.com/KaTeX/KaTeX/blob/main/src/katexImagesData.js) `minWidth`
/// for `\\xrightarrow`, `\\xleftrightarrow`, mhchem `\\xrightequilibrium`, etc.
pub(crate) fn katex_stretchy_min_width_em(label: &str) -> Option<f64> {
    katex_image_data(label).map(|d| d.min_width)
}

/// Look up the SVG path data for a named path key.
fn path_for_name(name: &str) -> Option<&'static str> {
    match name {
        "rightarrow" => Some(RIGHTARROW),
        "leftarrow" => Some(LEFTARROW),
        "doubleleftarrow" => Some(DOUBLELEFTARROW),
        "doublerightarrow" => Some(DOUBLERIGHTARROW),
        "leftharpoon" => Some(LEFTHARPOON),
        "leftharpoonplus" => Some(LEFTHARPOONPLUS),
        "leftharpoondown" => Some(LEFTHARPOONDOWN),
        "leftharpoondownplus" => Some(LEFTHARPOONDOWNPLUS),
        "rightharpoon" => Some(RIGHTHARPOON),
        "rightharpoonplus" => Some(RIGHTHARPOONPLUS),
        "rightharpoondown" => Some(RIGHTHARPOONDOWN),
        "rightharpoondownplus" => Some(RIGHTHARPOONDOWNPLUS),
        "baraboveshortleftharpoon" => Some(BARABOVESHORTLEFTHARPOON),
        "rightharpoonaboveshortbar" => Some(RIGHTHARPOONABOVESHORTBAR),
        "shortbaraboveleftharpoon" => Some(SHORTBARABOVELEFTHARPOON),
        "shortrightharpoonabovebar" => Some(SHORTRIGHTHARPOONABOVEBAR),
        "lefthook" => Some(LEFTHOOK),
        "righthook" => Some(RIGHTHOOK),
        "leftbrace" => Some(LEFTBRACE),
        "midbrace" => Some(MIDBRACE),
        "rightbrace" => Some(RIGHTBRACE),
        "leftbraceunder" => Some(LEFTBRACEUNDER),
        "midbraceunder" => Some(MIDBRACEUNDER),
        "rightbraceunder" => Some(RIGHTBRACEUNDER),
        "leftToFrom" => Some(LEFTTOFROM),
        "rightToFrom" => Some(RIGHTTOFROM),
        "baraboveleftarrow" => Some(BARABOVELEFTARROW),
        "rightarrowabovebar" => Some(RIGHTARROWABOVEBAR),
        "longequal" => Some(LONGEQUAL),
        "leftlinesegment" => Some(LEFTLINESEGMENT),
        "rightlinesegment" => Some(RIGHTLINESEGMENT),
        "leftmapsto" => Some(LEFTMAPSTO),
        "twoheadleftarrow" => Some(TWOHEADLEFTARROW),
        "twoheadrightarrow" => Some(TWOHEADRIGHTARROW),
        "leftgroup" => Some(LEFTGROUP),
        "leftgroupunder" => Some(LEFTGROUPUNDER),
        "rightgroup" => Some(RIGHTGROUP),
        "rightgroupunder" => Some(RIGHTGROUPUNDER),
        "leftbracketover" => Some(LEFTBRACKETOVER),
        "rightbracketover" => Some(RIGHTBRACKETOVER),
        "leftbracketunder" => Some(LEFTBRACKETUNDER),
        "rightbracketunder" => Some(RIGHTBRACKETUNDER),
        _ => None,
    }
}

/// Generalized KaTeX stretchy element renderer.
///
/// Returns `(commands, height_em)` where commands are centered at y=0
/// (shaft/midline at y≈0, extending ±height_em/2 above/below), or `None` if
/// the label is not in the lookup table.
pub fn katex_stretchy_path(label: &str, width_em: f64) -> Option<(Vec<PathCommand>, f64)> {
    let data = katex_image_data(label)?;
    let s = 1.0 / 1000.0;
    let height_em = data.vb_height * s;
    let vb_cy = data.vb_height / 2.0;
    let y_min = -height_em / 2.0;
    let y_max = height_em / 2.0;

    let make_cmds = |path_name: &str, x_shift: f64| -> Option<Vec<PathCommand>> {
        let svg_str = path_for_name(path_name)?;
        let raw = parse_svg_path(svg_str);
        Some(
            raw.iter()
                .map(|c| scale_cmd_twohead_uniform(c, s, vb_cy, x_shift))
                .collect(),
        )
    };

    match data.paths.len() {
        1 => {
            let x_shift = if data.align == Some("xMaxYMin") {
                width_em - 400_000.0 * s
            } else {
                0.0
            };
            let cmds = make_cmds(data.paths[0], x_shift)?;
            Some((
                clip_path_to_rect(&cmds, 0.0, width_em, y_min, y_max),
                height_em,
            ))
        }
        2 => {
            let x_r = width_em - 400_000.0 * s;
            let lc = make_cmds(data.paths[0], 0.0)?;
            let rc = make_cmds(data.paths[1], x_r)?;
            let is_xmapsto = data.paths[0] == "leftmapsto" && data.paths[1] == "rightarrow";
            let out = if is_xmapsto {
                // xmapsto: draw as one path so the horizontal shaft is guaranteed. KaTeX LEFTMAPSTO
                // has vertical bar at x=0..40 and shaft 40..400000; we draw left bar only, then
                // explicit shaft rect (from KaTeX viewBox 400000×522: y 241–281), then right arrow.
                // Shaft must not extend left of the vertical bar: use shaft_x_end >= left_bar_x_max.
                // Start the shaft slightly left of left_bar_x_max (~1px) so the intersection pixel
                // with the vertical bar is not clipped away by the boundary.
                let left_bar_x_max = 40.0 * s; // 0.04 em, right edge of left vertical bar
                let shaft_overlap = 0.1_f64; // ~1px in em to keep the corner pixel
                let shaft_x_start = (left_bar_x_max - shaft_overlap).max(0.0);
                let shaft_y_top = (241.0 - vb_cy) * s;
                let shaft_y_bot = (281.0 - vb_cy) * s;
                let shaft_x_end = x_r.max(left_bar_x_max);
                let mut cmds = clip_path_to_rect(&lc, 0.0, left_bar_x_max, y_min, y_max);
                if shaft_x_end > shaft_x_start {
                    cmds.push(PathCommand::MoveTo {
                        x: shaft_x_start,
                        y: shaft_y_top,
                    });
                    cmds.push(PathCommand::LineTo {
                        x: shaft_x_end,
                        y: shaft_y_top,
                    });
                    cmds.push(PathCommand::LineTo {
                        x: shaft_x_end,
                        y: shaft_y_bot,
                    });
                    cmds.push(PathCommand::LineTo {
                        x: shaft_x_start,
                        y: shaft_y_bot,
                    });
                    cmds.push(PathCommand::Close);
                }
                cmds.extend(clip_path_to_rect(&rc, 0.0, width_em, y_min, y_max));
                cmds
            } else {
                // Split at the midpoint so each half-arrow's shaft bars don't extend
                // into the opposite arrowhead region.  The left path owns [0, mid]
                // and the right path owns [mid, width_em].  This prevents shaft bar
                // rectangles from covering the opposite arrowhead (e.g. doubleleftarrow
                // shaft bars covering the doublerightarrow arrowhead area).
                let mid = width_em / 2.0;
                let mut cmds = clip_path_to_rect(&lc, 0.0, mid, y_min, y_max);
                cmds.extend(clip_path_to_rect(&rc, mid, width_em, y_min, y_max));
                cmds
            };
            Some((out, height_em))
        }
        3 => {
            let x_m = width_em / 2.0 - 200_000.0 * s;
            let x_r = width_em - 400_000.0 * s;
            let lc = make_cmds(data.paths[0], 0.0)?;
            let mc = make_cmds(data.paths[1], x_m)?;
            let rc = make_cmds(data.paths[2], x_r)?;
            // KaTeX `stretchy.ts` + `katex.scss`: three stacked spans with overflow hidden —
            // `.brace-left` 25.1%, `.brace-center` 50% at left 25%, `.brace-right` 25.1% from the
            // right. Each child SVG is 400em wide with `preserveAspectRatio` slice so only the
            // correct horizontal band shows. Clipping all three to `[0, width]` exposes the
            // middle glyph’s full 400-unit shaft on the entire width → thick bar past the curls.
            let key = label.trim_start_matches('\\');
            let w = width_em;
            let out = if key == "overbrace" || key == "underbrace" {
                let left_max = w * 0.251;
                let center_min = w * 0.25;
                let center_max = w * 0.75;
                let right_min = w * (1.0 - 0.251);
                let mut o = clip_path_to_rect(&lc, 0.0, left_max, y_min, y_max);
                o.extend(clip_path_to_rect(&mc, center_min, center_max, y_min, y_max));
                o.extend(clip_path_to_rect(&rc, right_min, w, y_min, y_max));
                o
            } else {
                let mut o = clip_path_to_rect(&lc, 0.0, w, y_min, y_max);
                o.extend(clip_path_to_rect(&mc, 0.0, w, y_min, y_max));
                o.extend(clip_path_to_rect(&rc, 0.0, w, y_min, y_max));
                o
            };
            Some((out, height_em))
        }
        _ => None,
    }
}

fn map_path_xy_horizontal_to_vertical_cd<F>(cmds: &[PathCommand], map: F) -> Vec<PathCommand>
where
    F: Fn(f64, f64) -> (f64, f64) + Copy,
{
    cmds.iter()
        .map(|c| match *c {
            PathCommand::MoveTo { x, y } => {
                let (nx, ny) = map(x, y);
                PathCommand::MoveTo { x: nx, y: ny }
            }
            PathCommand::LineTo { x, y } => {
                let (nx, ny) = map(x, y);
                PathCommand::LineTo { x: nx, y: ny }
            }
            PathCommand::CubicTo {
                x1,
                y1,
                x2,
                y2,
                x,
                y,
            } => {
                let (n1x, n1y) = map(x1, y1);
                let (n2x, n2y) = map(x2, y2);
                let (nx, ny) = map(x, y);
                PathCommand::CubicTo {
                    x1: n1x,
                    y1: n1y,
                    x2: n2x,
                    y2: n2y,
                    x: nx,
                    y: ny,
                }
            }
            PathCommand::QuadTo { x1, y1, x, y } => {
                let (n1x, n1y) = map(x1, y1);
                let (nx, ny) = map(x, y);
                PathCommand::QuadTo {
                    x1: n1x,
                    y1: n1y,
                    x: nx,
                    y: ny,
                }
            }
            PathCommand::Close => PathCommand::Close,
        })
        .collect()
}

/// Vertical `{CD}` ↑ / ↓: same filled KaTeX SVG as horizontal `\cdrightarrow`, rotated into the column.
///
/// Horizontal stretchy path uses x ∈ [0, `total_height_em`] (shaft length) and thin y-extent; we map
/// `(x,y) → (y_lateral + y, x - row_height)` (down) or mirrored for up so the arrowhead matches the
/// proven horizontal renderer in [`katex_stretchy_path`].
pub fn katex_cd_vert_arrow_from_rightarrow(
    down: bool,
    total_height_em: f64,
    axis_height_em: f64,
) -> Option<(Vec<PathCommand>, f64)> {
    let row_depth = (total_height_em / 2.0 - axis_height_em).max(0.0);
    let row_height = total_height_em - row_depth;
    let (cmds_h, lateral_em) = katex_stretchy_path("\\cdrightarrow", total_height_em)?;
    let w_lat = lateral_em;
    let cmds = if down {
        map_path_xy_horizontal_to_vertical_cd(&cmds_h, |xh, yh| (w_lat / 2.0 + yh, xh - row_height))
    } else {
        map_path_xy_horizontal_to_vertical_cd(&cmds_h, |xh, yh| (w_lat / 2.0 + yh, row_depth - xh))
    };
    Some((cmds, lateral_em))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_simple_path() {
        let cmds = parse_svg_path("M0 0 L10 20 Z");
        assert_eq!(cmds.len(), 3);
    }

    #[test]
    fn test_parse_katex_vec_path() {
        let cmds = scale_svg_path_thousandths(&parse_svg_path(KATEX_VEC_PATH));
        assert!(
            cmds.len() >= 8,
            "vec path should parse to multiple segments"
        );
        match cmds[0] {
            PathCommand::MoveTo { x, y } => {
                assert!((x - 0.377).abs() < 0.001);
                assert!((y - 0.02).abs() < 0.001);
            }
            _ => panic!("expected MoveTo"),
        }
    }

    #[test]
    fn test_parse_relative() {
        let cmds = parse_svg_path("M10 10 l5 5");
        assert_eq!(cmds.len(), 2);
        match cmds[1] {
            PathCommand::LineTo { x, y } => {
                assert!((x - 15.0).abs() < 0.01);
                assert!((y - 15.0).abs() < 0.01);
            }
            _ => panic!("expected LineTo"),
        }
    }

    #[test]
    fn test_parse_widehat1() {
        let cmds = parse_svg_path(WIDEHAT[0]);
        assert!(cmds.len() > 3);
    }

    #[test]
    fn test_katex_accent_widehat() {
        let result = katex_accent_path("\\widehat", 1.5, 1);
        assert!(result.is_some());
    }

    #[test]
    fn test_katex_accent_overgroup() {
        let result = katex_accent_path("\\overgroup", 1.5, 1);
        assert!(result.is_some());
    }

    #[test]
    fn test_tilde_path_coordinates() {
        let raw = parse_svg_path(TILDE[2]);
        assert_eq!(raw.len(), 16);
        // Verify the critical cubic that was previously broken by `\` line continuation
        match raw[7] {
            PathCommand::CubicTo { x1, y1, .. } => {
                assert!((x1 - 1141.3).abs() < 0.1, "x1 should be 1141.3, got {}", x1);
                assert!((y1 - 0.0).abs() < 0.1, "y1 should be 0, got {}", y1);
            }
            _ => panic!("expected CubicTo at index 7"),
        }
    }

    #[test]
    fn test_katex_stretchy_arrow_xtwohead() {
        let r = katex_stretchy_arrow_path("\\xtwoheadrightarrow", 2.0, 0.3);
        assert!(r.is_some());
        let cmds = r.unwrap();
        assert!(!cmds.is_empty());
        let l = katex_stretchy_arrow_path("\\xtwoheadleftarrow", 2.0, 0.3);
        assert!(l.is_some());
        assert!(!l.unwrap().is_empty());
        assert!(katex_stretchy_arrow_path("\\xrightarrow", 2.0, 0.3).is_some());
        assert!(katex_stretchy_arrow_path("\\xleftarrow", 2.0, 0.3).is_some());
    }

    #[test]
    fn test_katex_cd_vert_arrow_from_rightarrow() {
        let axis = 0.25_f64;
        for down in [true, false] {
            let r = katex_cd_vert_arrow_from_rightarrow(down, 2.5, axis);
            assert!(r.is_some(), "down={}", down);
            let (cmds, w) = r.unwrap();
            assert!(!cmds.is_empty());
            assert!(
                w > 0.45 && w < 0.58,
                "lateral extent should match horizontal arrow ink height ~0.522em, got {w}"
            );
        }
    }
}
