1818"""
1919
2020from collections import namedtuple
21+ import dataclasses
2122import enum
2223from functools import cache , lru_cache , partial , wraps
2324import logging
@@ -604,32 +605,30 @@ def __repr__(self):
604605
605606 def _width_of (self , char ):
606607 """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 )
612613
613614 def _height_depth_of (self , char ):
614615 """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+ ]
625624 # cmsyXX (symbols font) glyph 0 ("minus") has a nonzero descent
626625 # so that TeX aligns equations properly
627626 # (https://tex.stackexchange.com/q/526103/)
628627 # but we actually care about the rasterization depth to align
629628 # the dvipng-generated images.
630629 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
633632
634633
635634class Vf (Dvi ):
@@ -761,6 +760,22 @@ def _mul1220(num1, num2):
761760 return (num1 * num2 ) >> 20
762761
763762
763+ @dataclasses .dataclass (frozen = True , kw_only = 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+
764779class Tfm :
765780 """
766781 A TeX Font Metric file.
@@ -778,12 +793,7 @@ class Tfm:
778793 design_size : int
779794 Design size of the font (in 12.20 TeX points); unused because it is
780795 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.
785796 """
786- __slots__ = ('checksum' , 'design_size' , 'width' , 'height' , 'depth' )
787797
788798 def __init__ (self , filename ):
789799 _log .debug ('opening tfm file %s' , filename )
@@ -799,15 +809,26 @@ def __init__(self, filename):
799809 widths = struct .unpack (f'!{ nw } i' , file .read (4 * nw ))
800810 heights = struct .unpack (f'!{ nh } i' , file .read (4 * nh ))
801811 depths = struct .unpack (f'!{ nd } i' , file .read (4 * nd ))
802- self .width = {}
803- self .height = {}
804- self .depth = {}
812+ self ._glyph_metrics = {}
805813 for idx , char in enumerate (range (bc , ec + 1 )):
806814 byte0 = char_info [4 * idx ]
807815 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 }))
811832
812833
813834PsFont = namedtuple ('PsFont' , 'texname psname effects encoding filename' )
0 commit comments