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