mirror of
https://github.com/gryf/ebook-converter.git
synced 2026-01-02 00:22:25 +01:00
119 lines
4.0 KiB
Python
119 lines
4.0 KiB
Python
from io import BytesIO
|
|
from struct import calcsize, unpack, unpack_from
|
|
from collections import namedtuple
|
|
|
|
from ebook_converter.utils.fonts.utils import get_font_names2, get_font_characteristics
|
|
|
|
|
|
__license__ = 'GPL v3'
|
|
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
|
__docformat__ = 'restructuredtext en'
|
|
|
|
|
|
class UnsupportedFont(ValueError):
|
|
pass
|
|
|
|
|
|
FontCharacteristics = namedtuple('FontCharacteristics',
|
|
'weight, is_italic, is_bold, is_regular, fs_type, panose, width, is_oblique, is_wws, os2_version')
|
|
FontNames = namedtuple('FontNames',
|
|
'family_name, subfamily_name, full_name, preferred_family_name, preferred_subfamily_name, wws_family_name, wws_subfamily_name')
|
|
|
|
|
|
class FontMetadata(object):
|
|
|
|
def __init__(self, bytes_or_stream):
|
|
if not hasattr(bytes_or_stream, 'read'):
|
|
bytes_or_stream = BytesIO(bytes_or_stream)
|
|
f = bytes_or_stream
|
|
f.seek(0)
|
|
header = f.read(4)
|
|
if header not in {b'\x00\x01\x00\x00', b'OTTO'}:
|
|
raise UnsupportedFont('Not a supported sfnt variant')
|
|
|
|
self.is_otf = header == b'OTTO'
|
|
self.read_table_metadata(f)
|
|
self.read_names(f)
|
|
self.read_characteristics(f)
|
|
|
|
f.seek(0)
|
|
self.font_family = self.names.family_name
|
|
wt = self.characteristics.weight
|
|
if wt == 400:
|
|
wt = 'normal'
|
|
elif wt == 700:
|
|
wt = 'bold'
|
|
else:
|
|
wt = str(wt)
|
|
self.font_weight = wt
|
|
|
|
self.font_stretch = ('ultra-condensed', 'extra-condensed',
|
|
'condensed', 'semi-condensed', 'normal', 'semi-expanded',
|
|
'expanded', 'extra-expanded', 'ultra-expanded')[
|
|
self.characteristics.width-1]
|
|
if self.characteristics.is_oblique:
|
|
self.font_style = 'oblique'
|
|
elif self.characteristics.is_italic:
|
|
self.font_style = 'italic'
|
|
else:
|
|
self.font_style = 'normal'
|
|
|
|
def read_table_metadata(self, f):
|
|
f.seek(4)
|
|
num_tables = unpack(b'>H', f.read(2))[0]
|
|
# Start of table record entries
|
|
f.seek(4 + 4*2)
|
|
table_record = b'>4s3L'
|
|
sz = calcsize(table_record)
|
|
self.tables = {}
|
|
block = f.read(sz * num_tables)
|
|
for i in range(num_tables):
|
|
table_tag, table_checksum, table_offset, table_length = \
|
|
unpack_from(table_record, block, i*sz)
|
|
self.tables[table_tag.lower()] = (table_offset, table_length,
|
|
table_checksum)
|
|
|
|
def read_names(self, f):
|
|
if b'name' not in self.tables:
|
|
raise UnsupportedFont('This font has no name table')
|
|
toff, tlen = self.tables[b'name'][:2]
|
|
f.seek(toff)
|
|
table = f.read(tlen)
|
|
if len(table) != tlen:
|
|
raise UnsupportedFont('This font has a name table of incorrect length')
|
|
vals = get_font_names2(table, raw_is_table=True)
|
|
self.names = FontNames(*vals)
|
|
|
|
def read_characteristics(self, f):
|
|
if b'os/2' not in self.tables:
|
|
raise UnsupportedFont('This font has no OS/2 table')
|
|
toff, tlen = self.tables[b'os/2'][:2]
|
|
f.seek(toff)
|
|
table = f.read(tlen)
|
|
if len(table) != tlen:
|
|
raise UnsupportedFont('This font has an OS/2 table of incorrect length')
|
|
vals = get_font_characteristics(table, raw_is_table=True)
|
|
self.characteristics = FontCharacteristics(*vals)
|
|
|
|
def to_dict(self):
|
|
ans = {
|
|
'is_otf':self.is_otf,
|
|
'font-family':self.font_family,
|
|
'font-weight':self.font_weight,
|
|
'font-style':self.font_style,
|
|
'font-stretch':self.font_stretch
|
|
}
|
|
for f in self.names._fields:
|
|
ans[f] = getattr(self.names, f)
|
|
for f in self.characteristics._fields:
|
|
ans[f] = getattr(self.characteristics, f)
|
|
return ans
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import sys
|
|
with open(sys.argv[-1], 'rb') as f:
|
|
fm = FontMetadata(f)
|
|
import pprint
|
|
pprint.pprint(fm.to_dict())
|