@@ -249,16 +249,11 @@ def _get_info(self, fontname, font_class, sym, fontsize, dpi):
249249 )
250250
251251 def get_xheight (self , fontname , fontsize , dpi ):
252- font = self ._get_font (fontname )
253- font .set_size (fontsize , dpi )
254- pclt = font .get_sfnt_table ('pclt' )
255- if pclt is None :
256- # Some fonts don't store the xHeight, so we do a poor man's xHeight
257- metrics = self .get_metrics (
258- fontname , mpl .rcParams ['mathtext.default' ], 'x' , fontsize , dpi )
259- return metrics .iceberg
260- xHeight = (pclt ['xHeight' ] / 64.0 ) * (fontsize / 12.0 ) * (dpi / 100.0 )
261- return xHeight
252+ # Some fonts report the wrong x-height, while some don't store it, so
253+ # we do a poor man's x-height.
254+ metrics = self .get_metrics (
255+ fontname , mpl .rcParams ['mathtext.default' ], 'x' , fontsize , dpi )
256+ return metrics .iceberg
262257
263258 def get_underline_thickness (self , font , fontsize , dpi ):
264259 # This function used to grab underline thickness from the font
@@ -759,16 +754,44 @@ class FontConstantsBase:
759754 # integrals
760755 delta_integral = 0.1
761756
757+ # Percentage of x-height the numerator is shifted up in display style.
758+ num1 = 1.4
759+
760+ # Percentage of x-height the numerator is shifted up in text, script and
761+ # scriptscript styles if there is a fraction line.
762+ num2 = 1.5
763+
764+ # Percentage of x-height the numerator is shifted up in text, script and
765+ # scriptscript styles if there is no fraction line.
766+ num3 = 1.3
767+
768+ # Percentage of x-height the denominator is shifted down in display style.
769+ denom1 = 1.3
770+
771+ # Percentage of x-height the denominator is shifted down in text, script
772+ # and scriptscript styles.
773+ denom2 = 1.1
774+
762775
763776class ComputerModernFontConstants (FontConstantsBase ):
764- script_space = 0.075
765- subdrop = 0.2
766- sup1 = 0.45
767- sub1 = 0.2
768- sub2 = 0.3
769- delta = 0.075
777+ # Previously, the x-height of Computer Modern was obtained from the font
778+ # table. However, that x-height was greater than the the actual (rendered)
779+ # x-height by a factor of 1.771484375 (at font size 12, DPI 100 and hinting
780+ # type 32). Now that we're using the rendered x-height, some font constants
781+ # have been increased by the same factor to compensate.
782+ script_space = 0.132861328125
783+ subdrop = 0.354296875
784+ sup1 = 0.79716796875
785+ sub1 = 0.354296875
786+ sub2 = 0.5314453125
787+ delta = 0.132861328125
770788 delta_slanted = 0.3
771789 delta_integral = 0.3
790+ num1 = 1.5
791+ num2 = 1.5
792+ num3 = 1.5
793+ denom1 = 1.6
794+ denom2 = 1.2
772795
773796
774797class STIXFontConstants (FontConstantsBase ):
@@ -778,17 +801,27 @@ class STIXFontConstants(FontConstantsBase):
778801 delta = 0.05
779802 delta_slanted = 0.3
780803 delta_integral = 0.3
804+ num1 = 1.6
805+ num2 = 1.6
806+ num3 = 1.6
807+ denom1 = 1.6
781808
782809
783810class STIXSansFontConstants (FontConstantsBase ):
784811 script_space = 0.05
785812 sup1 = 0.8
786813 delta_slanted = 0.6
787814 delta_integral = 0.3
815+ num1 = 1.5
816+ num3 = 1.5
817+ denom1 = 1.5
788818
789819
790820class DejaVuSerifFontConstants (FontConstantsBase ):
791- pass
821+ num1 = 1.5
822+ num2 = 1.6
823+ num3 = 1.4
824+ denom1 = 1.4
792825
793826
794827class DejaVuSansFontConstants (FontConstantsBase ):
@@ -1832,6 +1865,7 @@ def set_names_and_parse_actions():
18321865 | p .underset
18331866 | p .sqrt
18341867 | p .overline
1868+ | p .auto_delim
18351869 )
18361870
18371871 p .simple <<= (
@@ -2156,6 +2190,19 @@ def is_slanted(self, nucleus):
21562190 def is_between_brackets (self , s , loc ):
21572191 return False
21582192
2193+ def is_char_node (self , node ):
2194+ # TeX defines a `char_node` as one which represents a single character,
2195+ # but also states that a `char_node` will never appear in a `Vlist`
2196+ # (node134). Further, nuclei made of one `Char` and nuclei made of
2197+ # multiple `Char`s have their superscripts and subscripts shifted by
2198+ # the same amount. In order to make Mathtext behave similarly, just
2199+ # check whether this node is a `Vlist` or has any `Vlist` descendants.
2200+ if isinstance (node , Vlist ):
2201+ return False
2202+ if not isinstance (node , Hlist ):
2203+ return True
2204+ return all (map (self .is_char_node , node .children ))
2205+
21592206 def subsuper (self , s , loc , toks ):
21602207 nucleus = toks .get ("nucleus" , Hbox (0 ))
21612208 subsuper = toks .get ("subsuper" , [])
@@ -2269,36 +2316,56 @@ def subsuper(self, s, loc, toks):
22692316 else :
22702317 subkern = 0
22712318
2319+ # Set the minimum shifts for the superscript and subscript (node756).
2320+ if self .is_char_node (nucleus ):
2321+ shift_up = 0
2322+ shift_down = 0
2323+ else :
2324+ drop_amount = constants .subdrop * xHeight * SHRINK_FACTOR
2325+ shift_up = nucleus .height - drop_amount
2326+ shift_down = nucleus .depth + drop_amount
2327+
22722328 if super is None :
2273- # node757
2329+ # Align subscript without superscript ( node757).
22742330 x = Hlist ([Kern (subkern ), sub ])
22752331 x .shrink ()
22762332 if self .is_dropsub (last_char ):
22772333 shift_down = lc_baseline + constants .subdrop * xHeight
22782334 else :
2279- shift_down = constants .sub1 * xHeight
2335+ shift_down = max (shift_down ,
2336+ constants .sub1 * xHeight ,
2337+ x .height - xHeight * 4 / 5 )
22802338 x .shift_amount = shift_down
22812339 else :
2340+ # Align superscript (node758).
22822341 x = Hlist ([Kern (superkern ), super ])
22832342 x .shrink ()
22842343 if self .is_dropsub (last_char ):
22852344 shift_up = lc_height - constants .subdrop * xHeight
22862345 else :
2287- shift_up = constants .sup1 * xHeight
2346+ shift_up = max (shift_up ,
2347+ constants .sup1 * xHeight ,
2348+ x .depth + xHeight / 4 )
22882349 if sub is None :
22892350 x .shift_amount = - shift_up
2290- else : # Both sub and superscript
2351+ else :
2352+ # Align subscript with superscript (node759).
22912353 y = Hlist ([Kern (subkern ), sub ])
22922354 y .shrink ()
22932355 if self .is_dropsub (last_char ):
22942356 shift_down = lc_baseline + constants .subdrop * xHeight
22952357 else :
2296- shift_down = constants .sub2 * xHeight
2297- # If sub and superscript collide, move super up
2298- clr = (2.0 * rule_thickness -
2358+ shift_down = max (shift_down , constants .sub2 * xHeight )
2359+ # If the subscript and superscript are too close to each other,
2360+ # move the subscript down.
2361+ clr = (4 * rule_thickness -
22992362 ((shift_up - x .depth ) - (y .height - shift_down )))
23002363 if clr > 0. :
2301- shift_up += clr
2364+ shift_down += clr
2365+ clr = xHeight * 4 / 5 - shift_up + x .depth
2366+ if clr > 0 :
2367+ shift_up += clr
2368+ shift_down -= clr
23022369 x = Vlist ([
23032370 x ,
23042371 Kern ((shift_up - x .depth ) - (y .height - shift_down )),
@@ -2322,32 +2389,73 @@ def _genfrac(self, ldelim, rdelim, rule, style, num, den):
23222389 state = self .get_state ()
23232390 thickness = state .get_current_underline_thickness ()
23242391
2392+ # The fraction line (if present) must be aligned with the minus sign.
2393+ # Therefore, the height of the latter from the baseline is the axis
2394+ # height.
2395+ metrics = state .font_output .get_metrics (
2396+ state .font , mpl .rcParams ['mathtext.default' ],
2397+ '\u2212 ' , state .fontsize , state .dpi )
2398+ axis_height = (metrics .ymax + metrics .ymin ) / 2
2399+ constants = _get_font_constant_set (state )
2400+ xHeight = state .font_output .get_xheight (
2401+ state .font , state .fontsize , state .dpi )
2402+
23252403 for _ in range (style .value ):
2404+ xHeight *= SHRINK_FACTOR
23262405 num .shrink ()
23272406 den .shrink ()
23282407 cnum = HCentered ([num ])
23292408 cden = HCentered ([den ])
23302409 width = max (num .width , den .width )
23312410 cnum .hpack (width , 'exactly' )
23322411 cden .hpack (width , 'exactly' )
2333- vlist = Vlist ([cnum , # numerator
2334- Vbox (0 , thickness * 2.0 ), # space
2335- Hrule (state , rule ), # rule
2336- Vbox (0 , thickness * 2.0 ), # space
2337- cden # denominator
2338- ])
2339-
2340- # Shift so the fraction line sits in the middle of the
2341- # equals sign
2342- metrics = state .font_output .get_metrics (
2343- state .font , mpl .rcParams ['mathtext.default' ],
2344- '=' , state .fontsize , state .dpi )
2345- shift = (cden .height -
2346- ((metrics .ymax + metrics .ymin ) / 2 -
2347- thickness * 3.0 ))
2348- vlist .shift_amount = shift
23492412
2350- result = [Hlist ([vlist , Hbox (thickness * 2. )])]
2413+ # Align the fraction with a fraction line (node743, node744 and
2414+ # node746).
2415+ if rule :
2416+ if style is self ._MathStyle .DISPLAYSTYLE :
2417+ num_shift_up = constants .num1 * xHeight
2418+ den_shift_down = constants .denom1 * xHeight
2419+ min_clr = 3 * rule
2420+ else :
2421+ num_shift_up = constants .num2 * xHeight
2422+ den_shift_down = constants .denom2 * xHeight
2423+ min_clr = rule
2424+ num_clr = max (
2425+ num_shift_up - cnum .depth - axis_height - rule / 2 , min_clr )
2426+ den_clr = max (
2427+ axis_height - rule / 2 + den_shift_down - cden .height , min_clr )
2428+ # Possible bug in fraction rendering. See GitHub PR 22852 comments.
2429+ vlist = Vlist ([cnum , # numerator
2430+ Vbox (0 , num_clr - rule ), # space
2431+ Hrule (state , rule ), # rule
2432+ Vbox (0 , den_clr + rule ), # space
2433+ cden # denominator
2434+ ])
2435+ vlist .shift_amount = cden .height + den_clr + rule / 2 - axis_height
2436+
2437+ # Align the fraction without a fraction line (node743, node744 and
2438+ # node745).
2439+ else :
2440+ if style is self ._MathStyle .DISPLAYSTYLE :
2441+ num_shift_up = constants .num1 * xHeight
2442+ den_shift_down = constants .denom1 * xHeight
2443+ min_clr = 7 * thickness
2444+ else :
2445+ num_shift_up = constants .num3 * xHeight
2446+ den_shift_down = constants .denom2 * xHeight
2447+ min_clr = 3 * thickness
2448+ def_clr = num_shift_up - cnum .depth + den_shift_down - cden .height
2449+ clr = max (def_clr , min_clr )
2450+ vlist = Vlist ([cnum , # numerator
2451+ Vbox (0 , clr ), # space
2452+ cden # denominator
2453+ ])
2454+ vlist .shift_amount = den_shift_down
2455+ if def_clr < min_clr :
2456+ vlist .shift_amount += (min_clr - def_clr ) / 2
2457+
2458+ result = [Hlist ([Hbox (thickness ), vlist , Hbox (thickness )])]
23512459 if ldelim or rdelim :
23522460 if ldelim == '' :
23532461 ldelim = '.'
0 commit comments