@@ -249,16 +249,11 @@ def _get_info(self, fontname, font_class, sym, fontsize, dpi):
249
249
)
250
250
251
251
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
262
257
263
258
def get_underline_thickness (self , font , fontsize , dpi ):
264
259
# This function used to grab underline thickness from the font
@@ -759,16 +754,44 @@ class FontConstantsBase:
759
754
# integrals
760
755
delta_integral = 0.1
761
756
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
+
762
775
763
776
class 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
770
788
delta_slanted = 0.3
771
789
delta_integral = 0.3
790
+ num1 = 1.5
791
+ num2 = 1.5
792
+ num3 = 1.5
793
+ denom1 = 1.6
794
+ denom2 = 1.2
772
795
773
796
774
797
class STIXFontConstants (FontConstantsBase ):
@@ -778,17 +801,27 @@ class STIXFontConstants(FontConstantsBase):
778
801
delta = 0.05
779
802
delta_slanted = 0.3
780
803
delta_integral = 0.3
804
+ num1 = 1.6
805
+ num2 = 1.6
806
+ num3 = 1.6
807
+ denom1 = 1.6
781
808
782
809
783
810
class STIXSansFontConstants (FontConstantsBase ):
784
811
script_space = 0.05
785
812
sup1 = 0.8
786
813
delta_slanted = 0.6
787
814
delta_integral = 0.3
815
+ num1 = 1.5
816
+ num3 = 1.5
817
+ denom1 = 1.5
788
818
789
819
790
820
class DejaVuSerifFontConstants (FontConstantsBase ):
791
- pass
821
+ num1 = 1.5
822
+ num2 = 1.6
823
+ num3 = 1.4
824
+ denom1 = 1.4
792
825
793
826
794
827
class DejaVuSansFontConstants (FontConstantsBase ):
@@ -1832,6 +1865,7 @@ def set_names_and_parse_actions():
1832
1865
| p .underset
1833
1866
| p .sqrt
1834
1867
| p .overline
1868
+ | p .auto_delim
1835
1869
)
1836
1870
1837
1871
p .simple <<= (
@@ -2156,6 +2190,19 @@ def is_slanted(self, nucleus):
2156
2190
def is_between_brackets (self , s , loc ):
2157
2191
return False
2158
2192
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
+
2159
2206
def subsuper (self , s , loc , toks ):
2160
2207
nucleus = toks .get ("nucleus" , Hbox (0 ))
2161
2208
subsuper = toks .get ("subsuper" , [])
@@ -2269,36 +2316,56 @@ def subsuper(self, s, loc, toks):
2269
2316
else :
2270
2317
subkern = 0
2271
2318
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
+
2272
2328
if super is None :
2273
- # node757
2329
+ # Align subscript without superscript ( node757).
2274
2330
x = Hlist ([Kern (subkern ), sub ])
2275
2331
x .shrink ()
2276
2332
if self .is_dropsub (last_char ):
2277
2333
shift_down = lc_baseline + constants .subdrop * xHeight
2278
2334
else :
2279
- shift_down = constants .sub1 * xHeight
2335
+ shift_down = max (shift_down ,
2336
+ constants .sub1 * xHeight ,
2337
+ x .height - xHeight * 4 / 5 )
2280
2338
x .shift_amount = shift_down
2281
2339
else :
2340
+ # Align superscript (node758).
2282
2341
x = Hlist ([Kern (superkern ), super ])
2283
2342
x .shrink ()
2284
2343
if self .is_dropsub (last_char ):
2285
2344
shift_up = lc_height - constants .subdrop * xHeight
2286
2345
else :
2287
- shift_up = constants .sup1 * xHeight
2346
+ shift_up = max (shift_up ,
2347
+ constants .sup1 * xHeight ,
2348
+ x .depth + xHeight / 4 )
2288
2349
if sub is None :
2289
2350
x .shift_amount = - shift_up
2290
- else : # Both sub and superscript
2351
+ else :
2352
+ # Align subscript with superscript (node759).
2291
2353
y = Hlist ([Kern (subkern ), sub ])
2292
2354
y .shrink ()
2293
2355
if self .is_dropsub (last_char ):
2294
2356
shift_down = lc_baseline + constants .subdrop * xHeight
2295
2357
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 -
2299
2362
((shift_up - x .depth ) - (y .height - shift_down )))
2300
2363
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
2302
2369
x = Vlist ([
2303
2370
x ,
2304
2371
Kern ((shift_up - x .depth ) - (y .height - shift_down )),
@@ -2322,32 +2389,73 @@ def _genfrac(self, ldelim, rdelim, rule, style, num, den):
2322
2389
state = self .get_state ()
2323
2390
thickness = state .get_current_underline_thickness ()
2324
2391
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
+
2325
2403
for _ in range (style .value ):
2404
+ xHeight *= SHRINK_FACTOR
2326
2405
num .shrink ()
2327
2406
den .shrink ()
2328
2407
cnum = HCentered ([num ])
2329
2408
cden = HCentered ([den ])
2330
2409
width = max (num .width , den .width )
2331
2410
cnum .hpack (width , 'exactly' )
2332
2411
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
2349
2412
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 )])]
2351
2459
if ldelim or rdelim :
2352
2460
if ldelim == '' :
2353
2461
ldelim = '.'
0 commit comments