1#!/usr/local/bin/python3.8
2# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
3
4
5__license__   = 'GPL v3'
6__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
7__docformat__ = 'restructuredtext en'
8
9from calibre.utils.fonts.utils import get_all_font_names
10from calibre.utils.fonts.sfnt.container import UnsupportedFont
11
12
13class FontMetrics:
14
15    '''
16    Get various metrics for the specified sfnt. All the metrics are returned in
17    units of pixels. To calculate a metric you have to specify the font size
18    (in pixels) and the horizontal stretch factor (between 0.0 and 1.0).
19    '''
20
21    def __init__(self, sfnt):
22        for table in (b'head', b'hhea', b'hmtx', b'cmap', b'OS/2', b'post',
23                      b'name', b'maxp'):
24            if table not in sfnt:
25                raise UnsupportedFont('This font has no %s table'%table)
26        self.sfnt = sfnt
27
28        self.head = self.sfnt[b'head']
29        hhea = self.sfnt[b'hhea']
30        hhea.read_data(self.sfnt[b'hmtx'], self.sfnt[b'maxp'].num_glyphs)
31        self.ascent = hhea.ascender
32        self.descent = hhea.descender
33        self.bbox = (self.head.x_min, self.head.y_min, self.head.x_max,
34                     self.head.y_max)
35        self._advance_widths = hhea.advance_widths
36        self.cmap = self.sfnt[b'cmap']
37        self.units_per_em = self.head.units_per_em
38        self.os2 = self.sfnt[b'OS/2']
39        self.os2.read_data()
40        self.post = self.sfnt[b'post']
41        self.post.read_data()
42        self.names = get_all_font_names(self.sfnt[b'name'].raw, raw_is_table=True)
43        self.is_otf = 'CFF ' in self.sfnt.tables
44        self._sig = hash(self.sfnt[b'name'].raw)
45
46        # Metrics for embedding in PDF
47        pdf_scale = self.pdf_scale = lambda x:int(round(x*1000./self.units_per_em))
48        self.pdf_ascent, self.pdf_descent = map(pdf_scale,
49                        (self.os2.typo_ascender, self.os2.typo_descender))
50        self.pdf_bbox = tuple(map(pdf_scale, self.bbox))
51        self.pdf_capheight = pdf_scale(getattr(self.os2, 'cap_height',
52                                               self.os2.typo_ascender))
53        self.pdf_avg_width = pdf_scale(self.os2.average_char_width)
54        self.pdf_stemv = 50 + int((self.os2.weight_class / 65.0) ** 2)
55
56    def __hash__(self):
57        return self._sig
58
59    @property
60    def postscript_name(self):
61        if 'postscript_name' in self.names:
62            return self.names['postscript_name'].replace(' ', '-')
63        try:
64            return self.names['full_name'].replace(' ', '-')
65        except KeyError:
66            return self.names['family_name'].replace(' ', '-')
67
68    def underline_thickness(self, pixel_size=12.0):
69        'Thickness for lines (in pixels) at the specified size'
70        yscale = pixel_size / self.units_per_em
71        return self.post.underline_thickness * yscale
72
73    def underline_position(self, pixel_size=12.0):
74        yscale = pixel_size / self.units_per_em
75        return self.post.underline_position * yscale
76
77    def overline_position(self, pixel_size=12.0):
78        yscale = pixel_size / self.units_per_em
79        return (self.ascent + 2) * yscale
80
81    def strikeout_size(self, pixel_size=12.0):
82        'The width of the strikeout line, in pixels'
83        yscale = pixel_size / self.units_per_em
84        return yscale * self.os2.strikeout_size
85
86    def strikeout_position(self, pixel_size=12.0):
87        'The displacement from the baseline to top of the strikeout line, in pixels'
88        yscale = pixel_size / self.units_per_em
89        return yscale * self.os2.strikeout_position
90
91    def advance_widths(self, string, pixel_size=12.0, stretch=1.0):
92        '''
93        Return the advance widths (in pixels) for all glyphs corresponding to
94        the characters in string at the specified pixel_size and stretch factor.
95        '''
96        if not isinstance(string, str):
97            raise ValueError('Must supply a unicode object')
98        chars = tuple(map(ord, string))
99        cmap = self.cmap.get_character_map(chars)
100        glyph_ids = (cmap[c] for c in chars)
101        pixel_size_x = stretch * pixel_size
102        xscale = pixel_size_x / self.units_per_em
103        return tuple(i*xscale for i in self.glyph_widths(glyph_ids))
104
105    def glyph_widths(self, glyph_ids):
106        last = len(self._advance_widths)
107        return tuple(self._advance_widths[i if i < last else -1] for i in
108                     glyph_ids)
109
110    def width(self, string, pixel_size=12.0, stretch=1.0):
111        'The width of the string at the specified pixel size and stretch, in pixels'
112        return sum(self.advance_widths(string, pixel_size, stretch))
113
114
115if __name__ == '__main__':
116    import sys
117    from calibre.utils.fonts.sfnt.container import Sfnt
118    with open(sys.argv[-1], 'rb') as f:
119        raw = f.read()
120    sfnt = Sfnt(raw)
121    m = FontMetrics(sfnt)
122    print('Ascent:', m.pdf_ascent)
123    print('Descent:', m.pdf_descent)
124    print('PDF BBox:', m.pdf_bbox)
125    print('CapHeight:', m.pdf_capheight)
126    print('AvgWidth:', m.pdf_avg_width)
127    print('ItalicAngle', m.post.italic_angle)
128    print('StemV', m.pdf_stemv)
129