18
18
"""
19
19
20
20
from collections import namedtuple
21
+ import dataclasses
21
22
import enum
22
23
from functools import cache , lru_cache , partial , wraps
23
24
import logging
@@ -604,32 +605,30 @@ def __repr__(self):
604
605
605
606
def _width_of (self , char ):
606
607
"""Width of char in dvi units."""
607
- width = self ._tfm .width . get (char , None )
608
- if width is not None :
609
- return _mul1220 ( width , self ._scale )
610
- _log . debug ( 'No width for char %d in font %s.' , char , self . texname )
611
- return 0
608
+ metrics = self ._tfm .get_metrics (char )
609
+ if metrics is None :
610
+ _log . debug ( 'No width for char %d in font %s.' , char , self .texname )
611
+ return 0
612
+ return _mul1220 ( metrics . tex_width , self . _scale )
612
613
613
614
def _height_depth_of (self , char ):
614
615
"""Height and depth of char in dvi units."""
615
- result = []
616
- for metric , name in ((self ._tfm .height , "height" ),
617
- (self ._tfm .depth , "depth" )):
618
- value = metric .get (char , None )
619
- if value is None :
620
- _log .debug ('No %s for char %d in font %s' ,
621
- name , char , self .texname )
622
- result .append (0 )
623
- else :
624
- result .append (_mul1220 (value , self ._scale ))
616
+ metrics = self ._tfm .get_metrics (char )
617
+ if metrics is None :
618
+ _log .debug ('No metrics for char %d in font %s' , char , self .texname )
619
+ return [0 , 0 ]
620
+ hd = [
621
+ _mul1220 (metrics .tex_height , self ._scale ),
622
+ _mul1220 (metrics .tex_depth , self ._scale ),
623
+ ]
625
624
# cmsyXX (symbols font) glyph 0 ("minus") has a nonzero descent
626
625
# so that TeX aligns equations properly
627
626
# (https://tex.stackexchange.com/q/526103/)
628
627
# but we actually care about the rasterization depth to align
629
628
# the dvipng-generated images.
630
629
if re .match (br'^cmsy\d+$' , self .texname ) and char == 0 :
631
- result [- 1 ] = 0
632
- return result
630
+ hd [- 1 ] = 0
631
+ return hd
633
632
634
633
635
634
class Vf (Dvi ):
@@ -761,6 +760,22 @@ def _mul1220(num1, num2):
761
760
return (num1 * num2 ) >> 20
762
761
763
762
763
+ @dataclasses .dataclass (frozen = True )
764
+ class TexMetrics :
765
+ """
766
+ Metrics of a glyph, with TeX semantics.
767
+
768
+ TeX metrics have different semantics from FreeType metrics: tex_width
769
+ corresponds to FreeType's ``advance`` (i.e., including whitespace padding);
770
+ tex_height to ``bearingY`` (how much the glyph extends over the baseline);
771
+ tex_depth to ``height - bearingY`` (how much the glyph extends under the
772
+ baseline, as a positive number).
773
+ """
774
+ tex_width : int
775
+ tex_height : int
776
+ tex_depth : int
777
+
778
+
764
779
class Tfm :
765
780
"""
766
781
A TeX Font Metric file.
@@ -778,12 +793,7 @@ class Tfm:
778
793
design_size : int
779
794
Design size of the font (in 12.20 TeX points); unused because it is
780
795
overridden by the scale factor specified in the dvi file.
781
- width, height, depth : dict
782
- Dimensions of each character, need to be scaled by the factor
783
- specified in the dvi file. These are dicts because indexing may
784
- not start from 0.
785
796
"""
786
- __slots__ = ('checksum' , 'design_size' , 'width' , 'height' , 'depth' )
787
797
788
798
def __init__ (self , filename ):
789
799
_log .debug ('opening tfm file %s' , filename )
@@ -799,15 +809,26 @@ def __init__(self, filename):
799
809
widths = struct .unpack (f'!{ nw } i' , file .read (4 * nw ))
800
810
heights = struct .unpack (f'!{ nh } i' , file .read (4 * nh ))
801
811
depths = struct .unpack (f'!{ nd } i' , file .read (4 * nd ))
802
- self .width = {}
803
- self .height = {}
804
- self .depth = {}
812
+ self ._glyph_metrics = {}
805
813
for idx , char in enumerate (range (bc , ec + 1 )):
806
814
byte0 = char_info [4 * idx ]
807
815
byte1 = char_info [4 * idx + 1 ]
808
- self .width [char ] = widths [byte0 ]
809
- self .height [char ] = heights [byte1 >> 4 ]
810
- self .depth [char ] = depths [byte1 & 0xf ]
816
+ self ._glyph_metrics [char ] = TexMetrics (
817
+ tex_width = widths [byte0 ],
818
+ tex_height = heights [byte1 >> 4 ],
819
+ tex_depth = depths [byte1 & 0xf ],
820
+ )
821
+
822
+ def get_metrics (self , idx ):
823
+ """Return a glyph's TexMetrics, or None if unavailable."""
824
+ return self ._glyph_metrics .get (idx )
825
+
826
+ width = _api .deprecated ("3.11" , alternative = "get_metrics" )(
827
+ property (lambda self : {c : m .tex_width for c , m in self ._glyph_metrics }))
828
+ height = _api .deprecated ("3.11" , alternative = "get_metrics" )(
829
+ property (lambda self : {c : m .tex_height for c , m in self ._glyph_metrics }))
830
+ depth = _api .deprecated ("3.11" , alternative = "get_metrics" )(
831
+ property (lambda self : {c : m .tex_depth for c , m in self ._glyph_metrics }))
811
832
812
833
813
834
PsFont = namedtuple ('PsFont' , 'texname psname effects encoding filename' )
0 commit comments