KaTeX/metrics/parse_tfm.py
Kevin Barabash 67147b18ac Adds font metrics to support font commands.
This is part 1 of 3.  Rendering, screenshots, MathML, and unit tests will
follow in susbequent pull requests.
2015-07-10 21:50:04 -06:00

212 lines
6.4 KiB
Python

class CharInfoWord(object):
def __init__(self, word):
b1, b2, b3, b4 = (word >> 24,
(word & 0xff0000) >> 16,
(word & 0xff00) >> 8,
word & 0xff)
self.width_index = b1
self.height_index = b2 >> 4
self.depth_index = b2 & 0x0f
self.italic_index = (b3 & 0b11111100) >> 2
self.tag = b3 & 0b11
self.remainder = b4
def has_ligkern(self):
return self.tag == 1
def ligkern_start(self):
return self.remainder
class LigKernProgram(object):
def __init__(self, program):
self.program = program
def execute(self, start, next_char):
curr_instruction = start
while True:
instruction = self.program[curr_instruction]
(skip, inst_next_char, op, remainder) = instruction
if inst_next_char == next_char:
if op < 128:
# Don't worry about ligatures for now, we only need kerns
return None
else:
return 256 * (op - 128) + remainder
elif skip >= 128:
return None
else:
curr_instruction += 1 + skip
class TfmCharMetrics(object):
def __init__(self, width, height, depth, italic, kern_table):
self.width = width
self.height = height
self.depth = depth
self.italic_correction = italic
self.kern_table = kern_table
class TfmFile(object):
def __init__(self, start_char, end_char, char_info, width_table,
height_table, depth_table, italic_table, ligkern_table,
kern_table):
self.start_char = start_char
self.end_char = end_char
self.char_info = char_info
self.width_table = width_table
self.height_table = height_table
self.depth_table = depth_table
self.italic_table = italic_table
self.ligkern_program = LigKernProgram(ligkern_table)
self.kern_table = kern_table
def get_char_metrics(self, char_num, fix_rsfs=False):
"""Return glyph metrics for a unicode code point.
Arguments:
char_num: a unicode code point
fix_rsfs: adjust for rsfs10.tfm's different indexing system
"""
if char_num < self.start_char or char_num > self.end_char:
raise RuntimeError("Invalid character number")
if fix_rsfs:
# all of the char_nums contained start from zero in rsfs10.tfm
info = self.char_info[char_num - self.start_char]
else:
info = self.char_info[char_num + self.start_char]
char_kern_table = {}
if info.has_ligkern():
for char in range(self.start_char, self.end_char + 1):
kern = self.ligkern_program.execute(info.ligkern_start(), char)
if kern:
char_kern_table[char] = self.kern_table[kern]
return TfmCharMetrics(
self.width_table[info.width_index],
self.height_table[info.height_index],
self.depth_table[info.depth_index],
self.italic_table[info.italic_index],
char_kern_table)
class TfmReader(object):
def __init__(self, f):
self.f = f
def read_byte(self):
return ord(self.f.read(1))
def read_halfword(self):
b1 = self.read_byte()
b2 = self.read_byte()
return (b1 << 8) | b2
def read_word(self):
b1 = self.read_byte()
b2 = self.read_byte()
b3 = self.read_byte()
b4 = self.read_byte()
return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4
def read_fixword(self):
word = self.read_word()
neg = False
if word & 0x80000000:
neg = True
word = (-word & 0xffffffff)
return (-1 if neg else 1) * word / float(1 << 20)
def read_bcpl(self, length):
str_length = self.read_byte()
data = self.f.read(length - 1)
return data[:str_length]
def read_tfm_file(file_name):
with open(file_name, 'rb') as f:
reader = TfmReader(f)
# file_size
reader.read_halfword()
header_size = reader.read_halfword()
start_char = reader.read_halfword()
end_char = reader.read_halfword()
width_table_size = reader.read_halfword()
height_table_size = reader.read_halfword()
depth_table_size = reader.read_halfword()
italic_table_size = reader.read_halfword()
ligkern_table_size = reader.read_halfword()
kern_table_size = reader.read_halfword()
# extensible_table_size
reader.read_halfword()
# parameter_table_size
reader.read_halfword()
# checksum
reader.read_word()
# design_size
reader.read_fixword()
if header_size > 2:
# coding_scheme
reader.read_bcpl(40)
if header_size > 12:
# font_family
reader.read_bcpl(20)
for i in range(header_size - 17):
reader.read_word()
char_info = []
for i in range(start_char, end_char + 1):
char_info.append(CharInfoWord(reader.read_word()))
width_table = []
for i in range(width_table_size):
width_table.append(reader.read_fixword())
height_table = []
for i in range(height_table_size):
height_table.append(reader.read_fixword())
depth_table = []
for i in range(depth_table_size):
depth_table.append(reader.read_fixword())
italic_table = []
for i in range(italic_table_size):
italic_table.append(reader.read_fixword())
ligkern_table = []
for i in range(ligkern_table_size):
skip = reader.read_byte()
next_char = reader.read_byte()
op = reader.read_byte()
remainder = reader.read_byte()
ligkern_table.append((skip, next_char, op, remainder))
kern_table = []
for i in range(kern_table_size):
kern_table.append(reader.read_fixword())
# There is more information, like the ligkern, kern, extensible, and
# param table, but we don't need these for now
return TfmFile(start_char, end_char, char_info, width_table,
height_table, depth_table, italic_table,
ligkern_table, kern_table)