parse_tfm.py (6524B)
1 class CharInfoWord(object): 2 def __init__(self, word): 3 b1, b2, b3, b4 = (word >> 24, 4 (word & 0xff0000) >> 16, 5 (word & 0xff00) >> 8, 6 word & 0xff) 7 8 self.width_index = b1 9 self.height_index = b2 >> 4 10 self.depth_index = b2 & 0x0f 11 self.italic_index = (b3 & 0b11111100) >> 2 12 self.tag = b3 & 0b11 13 self.remainder = b4 14 15 def has_ligkern(self): 16 return self.tag == 1 17 18 def ligkern_start(self): 19 return self.remainder 20 21 22 class LigKernProgram(object): 23 def __init__(self, program): 24 self.program = program 25 26 def execute(self, start, next_char): 27 curr_instruction = start 28 while True: 29 instruction = self.program[curr_instruction] 30 (skip, inst_next_char, op, remainder) = instruction 31 32 if inst_next_char == next_char: 33 if op < 128: 34 # Don't worry about ligatures for now, we only need kerns 35 return None 36 else: 37 return 256 * (op - 128) + remainder 38 elif skip >= 128: 39 return None 40 else: 41 curr_instruction += 1 + skip 42 43 44 class TfmCharMetrics(object): 45 def __init__(self, width, height, depth, italic, kern_table): 46 self.width = width 47 self.height = height 48 self.depth = depth 49 self.italic_correction = italic 50 self.kern_table = kern_table 51 52 53 class TfmFile(object): 54 def __init__(self, start_char, end_char, char_info, width_table, 55 height_table, depth_table, italic_table, ligkern_table, 56 kern_table): 57 self.start_char = start_char 58 self.end_char = end_char 59 self.char_info = char_info 60 self.width_table = width_table 61 self.height_table = height_table 62 self.depth_table = depth_table 63 self.italic_table = italic_table 64 self.ligkern_program = LigKernProgram(ligkern_table) 65 self.kern_table = kern_table 66 67 def get_char_metrics(self, char_num, fix_rsfs=False): 68 """Return glyph metrics for a unicode code point. 69 70 Arguments: 71 char_num: a unicode code point 72 fix_rsfs: adjust for rsfs10.tfm's different indexing system 73 """ 74 if char_num < self.start_char or char_num > self.end_char: 75 raise RuntimeError("Invalid character number") 76 77 if fix_rsfs: 78 # all of the char_nums contained start from zero in rsfs10.tfm 79 info = self.char_info[char_num - self.start_char] 80 else: 81 info = self.char_info[char_num + self.start_char] 82 83 char_kern_table = {} 84 if info.has_ligkern(): 85 for char in range(self.start_char, self.end_char + 1): 86 kern = self.ligkern_program.execute(info.ligkern_start(), char) 87 if kern: 88 char_kern_table[char] = self.kern_table[kern] 89 90 return TfmCharMetrics( 91 self.width_table[info.width_index], 92 self.height_table[info.height_index], 93 self.depth_table[info.depth_index], 94 self.italic_table[info.italic_index], 95 char_kern_table) 96 97 98 class TfmReader(object): 99 def __init__(self, f): 100 self.f = f 101 102 def read_byte(self): 103 return ord(self.f.read(1)) 104 105 def read_halfword(self): 106 b1 = self.read_byte() 107 b2 = self.read_byte() 108 return (b1 << 8) | b2 109 110 def read_word(self): 111 b1 = self.read_byte() 112 b2 = self.read_byte() 113 b3 = self.read_byte() 114 b4 = self.read_byte() 115 return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4 116 117 def read_fixword(self): 118 word = self.read_word() 119 120 neg = False 121 if word & 0x80000000: 122 neg = True 123 word = (-word & 0xffffffff) 124 125 return (-1 if neg else 1) * word / float(1 << 20) 126 127 def read_bcpl(self, length): 128 str_length = self.read_byte() 129 data = self.f.read(length - 1) 130 return data[:str_length] 131 132 133 def read_tfm_file(file_name): 134 with open(file_name, 'rb') as f: 135 reader = TfmReader(f) 136 137 # file_size 138 reader.read_halfword() 139 header_size = reader.read_halfword() 140 141 start_char = reader.read_halfword() 142 end_char = reader.read_halfword() 143 144 width_table_size = reader.read_halfword() 145 height_table_size = reader.read_halfword() 146 depth_table_size = reader.read_halfword() 147 italic_table_size = reader.read_halfword() 148 149 ligkern_table_size = reader.read_halfword() 150 kern_table_size = reader.read_halfword() 151 152 # extensible_table_size 153 reader.read_halfword() 154 # parameter_table_size 155 reader.read_halfword() 156 157 # checksum 158 reader.read_word() 159 # design_size 160 reader.read_fixword() 161 162 if header_size > 2: 163 # coding_scheme 164 reader.read_bcpl(40) 165 166 if header_size > 12: 167 # font_family 168 reader.read_bcpl(20) 169 170 for i in range(header_size - 17): 171 reader.read_word() 172 173 char_info = [] 174 for i in range(start_char, end_char + 1): 175 char_info.append(CharInfoWord(reader.read_word())) 176 177 width_table = [] 178 for i in range(width_table_size): 179 width_table.append(reader.read_fixword()) 180 181 height_table = [] 182 for i in range(height_table_size): 183 height_table.append(reader.read_fixword()) 184 185 depth_table = [] 186 for i in range(depth_table_size): 187 depth_table.append(reader.read_fixword()) 188 189 italic_table = [] 190 for i in range(italic_table_size): 191 italic_table.append(reader.read_fixword()) 192 193 ligkern_table = [] 194 for i in range(ligkern_table_size): 195 skip = reader.read_byte() 196 next_char = reader.read_byte() 197 op = reader.read_byte() 198 remainder = reader.read_byte() 199 200 ligkern_table.append((skip, next_char, op, remainder)) 201 202 kern_table = [] 203 for i in range(kern_table_size): 204 kern_table.append(reader.read_fixword()) 205 206 # There is more information, like the ligkern, kern, extensible, and 207 # param table, but we don't need these for now 208 209 return TfmFile(start_char, end_char, char_info, width_table, 210 height_table, depth_table, italic_table, 211 ligkern_table, kern_table)