@@ -1213,10 +1213,22 @@ fn layout_supsub(
12131213 // (KaTeX `margin-right: italic`), so we must not add `glyph_italic` again here.
12141214 let italic_correction = 0.0 ;
12151215
1216- // KaTeX `supsub.js`: for SymbolNode bases, subscripts get `margin-left: -base.italic` so they
1217- // are not shifted by the base's italic correction (e.g. ∫_{A_1}).
1216+ // KaTeX `supsub.js`: only a direct `SymbolNode` base (plus the synthetic `\oiint` /
1217+ // `\oiiint` operators) gets `margin-left: -base.italic`. In particular, spans such as
1218+ // `\text{\textit{CPI}}` and `{x}` must not inherit the italic correction of their last glyph.
12181219 let sub_h_kern = if sub_box. is_some ( ) && !center_scripts {
1219- -glyph_italic ( & base_box)
1220+ let direct_italic = direct_glyph_italic ( & base_box) ;
1221+ let is_oiint = matches ! (
1222+ base,
1223+ Some ( ParseNode :: Op {
1224+ name: Some ( name) ,
1225+ ..
1226+ } ) if name == "\\ oiint" || name == "\\ oiiint"
1227+ ) ;
1228+ let base_italic = direct_italic
1229+ . or_else ( || is_oiint. then ( || first_glyph_italic ( & base_box) ) . flatten ( ) )
1230+ . unwrap_or ( 0.0 ) ;
1231+ -base_italic
12201232 } else {
12211233 0.0
12221234 } ;
@@ -1782,24 +1794,28 @@ fn layout_operatorname(body: &[ParseNode], options: &LayoutOptions) -> LayoutBox
17821794/// `\vec` KaTeX SVG: nudge slightly right to match KaTeX reference.
17831795const VEC_SKEW_EXTRA_RIGHT_EM : f64 = 0.018 ;
17841796
1785- /// Extract the italic correction of the base glyph.
1786- /// Used by superscripts: KaTeX adds margin-right = italic_correction to italic math characters,
1787- /// so the superscript starts at advance_width + italic_correction (not just advance_width).
1788- fn glyph_italic ( lb : & LayoutBox ) -> f64 {
1789- let mut current = lb;
1790- loop {
1791- match & current. content {
1792- BoxContent :: Glyph { font_id, char_code } => {
1793- return get_char_metrics ( * font_id, * char_code)
1794- . map ( |m| m. italic )
1795- . unwrap_or ( 0.0 ) ;
1796- }
1797- BoxContent :: HBox ( children) => match children. last ( ) {
1798- Some ( last) => current = last,
1799- None => return 0.0 ,
1800- } ,
1801- _ => return 0.0 ,
1797+ /// Extract the italic correction only when this box itself is a glyph.
1798+ ///
1799+ /// This distinction mirrors KaTeX's `base instanceof SymbolNode` check. Recursing through an
1800+ /// `HBox` would incorrectly treat text/group spans as symbols and pull their subscripts left.
1801+ fn direct_glyph_italic ( lb : & LayoutBox ) -> Option < f64 > {
1802+ match & lb. content {
1803+ BoxContent :: Glyph { font_id, char_code } => {
1804+ get_char_metrics ( * font_id, * char_code) . map ( |m| m. italic )
18021805 }
1806+ _ => None ,
1807+ }
1808+ }
1809+
1810+ /// Find the first glyph inside a box.
1811+ ///
1812+ /// KaTeX special-cases `\oiint` and `\oiiint`: their overlay makes the base a span rather than a
1813+ /// `SymbolNode`, but the underlying integral glyph's italic correction still applies.
1814+ fn first_glyph_italic ( lb : & LayoutBox ) -> Option < f64 > {
1815+ match & lb. content {
1816+ BoxContent :: Glyph { .. } => direct_glyph_italic ( lb) ,
1817+ BoxContent :: HBox ( children) => children. iter ( ) . find_map ( first_glyph_italic) ,
1818+ _ => None ,
18031819 }
18041820}
18051821
0 commit comments