@@ -198,7 +198,7 @@ fn layout_node(node: &ParseNode, options: &LayoutOptions) -> LayoutBox {
198198 let bar_thickness = if * has_bar_line {
199199 bar_size
200200 . as_ref ( )
201- . map ( |m| m . number * options. metrics ( ) . default_rule_thickness )
201+ . map ( |m| measurement_to_em ( m , options) )
202202 . unwrap_or ( options. metrics ( ) . default_rule_thickness )
203203 } else {
204204 0.0
@@ -1427,7 +1427,12 @@ fn layout_accent(
14271427 0.0
14281428 } ;
14291429
1430- let gap = if use_arrow_path { 0.12 } else { 0.0 } ;
1430+ // gap = clearance between body top and bottom of accent SVG.
1431+ // For arrow accents, the SVG path is centered (height=h/2, depth=h/2).
1432+ // The gap prevents the visible arrowhead boundary from overlapping with body top.
1433+ let gap = if use_arrow_path {
1434+ if label == "\\ Overrightarrow" { 0.21 } else { 0.12 }
1435+ } else { 0.0 } ;
14311436
14321437 let clearance = if is_below {
14331438 body_box. height + body_box. depth + accent_box. depth + gap
@@ -2423,12 +2428,16 @@ fn layout_overline(body: &ParseNode, options: &LayoutOptions) -> LayoutBox {
24232428 let metrics = options. metrics ( ) ;
24242429 let rule = metrics. default_rule_thickness ;
24252430
2431+ // Total height: body height + 2*rule clearance + rule thickness = body.height + 3*rule
24262432 let height = body_box. height + 3.0 * rule;
24272433 LayoutBox {
24282434 width : body_box. width ,
24292435 height,
24302436 depth : body_box. depth ,
2431- content : BoxContent :: HBox ( vec ! [ body_box] ) ,
2437+ content : BoxContent :: Overline {
2438+ body : Box :: new ( body_box) ,
2439+ rule_thickness : rule,
2440+ } ,
24322441 color : options. color ,
24332442 }
24342443}
@@ -2438,12 +2447,16 @@ fn layout_underline(body: &ParseNode, options: &LayoutOptions) -> LayoutBox {
24382447 let metrics = options. metrics ( ) ;
24392448 let rule = metrics. default_rule_thickness ;
24402449
2450+ // Total depth: body depth + 2*rule clearance + rule thickness = body.depth + 3*rule
24412451 let depth = body_box. depth + 3.0 * rule;
24422452 LayoutBox {
24432453 width : body_box. width ,
24442454 height : body_box. height ,
24452455 depth,
2446- content : BoxContent :: HBox ( vec ! [ body_box] ) ,
2456+ content : BoxContent :: Underline {
2457+ body : Box :: new ( body_box) ,
2458+ rule_thickness : rule,
2459+ } ,
24472460 color : options. color ,
24482461 }
24492462}
@@ -2463,7 +2476,13 @@ fn layout_spacing_command(text: &str, options: &LayoutOptions) -> LayoutBox {
24632476 "\\ !" | "\\ negthinspace" => -3.0 * mu,
24642477 "\\ negmedspace" => -4.0 * mu,
24652478 "\\ negthickspace" => -5.0 * mu,
2466- "~" | "\\ nobreakspace" | "\\ " => metrics. space ,
2479+ "~" | "\\ nobreakspace" | "\\ " | "\\ space" => {
2480+ // KaTeX renders these by placing the U+00A0 glyph (char 160) via mathsym.
2481+ // Look up its width from MainRegular; fall back to 0.25em (the font-defined value).
2482+ get_char_metrics ( FontId :: MainRegular , 160 )
2483+ . map ( |m| m. width )
2484+ . unwrap_or ( 0.25 )
2485+ }
24672486 "\\ quad" => metrics. quad ,
24682487 "\\ qquad" => 2.0 * metrics. quad ,
24692488 "\\ enspace" => metrics. quad / 2.0 ,
@@ -2687,37 +2706,50 @@ fn layout_xarrow(
26872706 color : options. color ,
26882707 } ;
26892708
2709+ // KaTeX positions xarrows centered on the math axis, with a 0.111em (2mu) gap
2710+ // between the arrow and the text above/below (see amsmath.dtx reference).
26902711 let metrics = options. metrics ( ) ;
2691- let _sp1 = 0.111 ; // bigOpSpacing1 — above the superscript
2692- let _sp2 = 0.166 ; // bigOpSpacing2 — below the subscript
2693- let sp3 = 0.2 ; // bigOpSpacing3 — gap between base and script
2694- let sp5 = metrics. big_op_spacing5 ;
2712+ let axis = metrics. axis_height ; // 0.25em
2713+ let arrow_half = actual_arrow_h / 2.0 ;
2714+ let gap = 0.111 ; // 2mu gap (KaTeX constant)
2715+
2716+ // Center the arrow on the math axis by shifting it up.
2717+ let base_shift = -axis;
2718+
2719+ // sup_kern: gap between arrow top and text bottom.
2720+ // In the OpLimits renderer:
2721+ // sup_y = y - (arrow_half - base_shift) - sup_kern - sup_box.depth * ratio
2722+ // = y - (arrow_half + axis) - sup_kern - sup_box.depth * ratio
2723+ // KaTeX: text_baseline = -(axis + arrow_half + gap)
2724+ // (with extra -= depth when depth > 0.25, but that's rare for typical text)
2725+ // Matching: sup_kern = gap
2726+ let sup_kern = gap;
2727+ let sub_kern = gap;
26952728
2696- let total_w = arrow_w;
26972729 let sup_h = body_box. height * sup_ratio;
26982730 let sup_d = body_box. depth * sup_ratio;
26992731
2700- let height = arrow_box. height + sp3 + sup_h + sup_d + sp5;
2701- let mut depth = arrow_box. depth + sp5;
2732+ // Height: from baseline to top of upper text
2733+ let height = axis + arrow_half + gap + sup_h + sup_d;
2734+ // Depth: arrow bottom below baseline = arrow_half - axis
2735+ let mut depth = ( arrow_half - axis) . max ( 0.0 ) ;
27022736
27032737 if let Some ( ref bel) = below_box {
27042738 let sub_h = bel. height * sub_ratio;
27052739 let sub_d = bel. depth * sub_ratio;
2706- depth = arrow_box. depth + sp3 + sub_h + sub_d + sp5;
2740+ // Lower text positioned symmetrically below the arrow
2741+ depth = ( arrow_half - axis) + gap + sub_h + sub_d;
27072742 }
27082743
2709- let sup_kern = sp3;
2710- let sub_kern = sp3;
2711-
27122744 LayoutBox {
2713- width : total_w ,
2745+ width : arrow_w ,
27142746 height,
27152747 depth,
27162748 content : BoxContent :: OpLimits {
27172749 base : Box :: new ( arrow_box) ,
27182750 sup : Some ( Box :: new ( body_box) ) ,
27192751 sub : below_box. map ( Box :: new) ,
2720- base_shift : 0.0 ,
2752+ base_shift,
27212753 sup_kern,
27222754 sub_kern,
27232755 slant : 0.0 ,
0 commit comments