%PDF- %PDF-
| Direktori : /usr/lib/calibre/calibre/utils/fonts/sfnt/ |
| Current File : //usr/lib/calibre/calibre/utils/fonts/sfnt/container.py |
#!/usr/bin/env python3
# License: GPLv3 Copyright: 2012, Kovid Goyal <kovid at kovidgoyal.net>
from collections import OrderedDict
from io import BytesIO
from struct import calcsize, pack
from calibre.utils.fonts.sfnt import UnknownTable, align_block, max_power_of_two
from calibre.utils.fonts.sfnt.cff.table import CFFTable
from calibre.utils.fonts.sfnt.cmap import CmapTable
from calibre.utils.fonts.sfnt.errors import UnsupportedFont
from calibre.utils.fonts.sfnt.glyf import GlyfTable
from calibre.utils.fonts.sfnt.gsub import GSUBTable
from calibre.utils.fonts.sfnt.head import (
HeadTable, HorizontalHeader, OS2Table, PostTable, VerticalHeader
)
from calibre.utils.fonts.sfnt.kern import KernTable
from calibre.utils.fonts.sfnt.loca import LocaTable
from calibre.utils.fonts.sfnt.maxp import MaxpTable
from calibre.utils.fonts.utils import checksum_of_block, get_tables, verify_checksums
# OpenType spec: http://www.microsoft.com/typography/otspec/otff.htm
class Sfnt:
TABLE_MAP = {
b'head' : HeadTable,
b'hhea' : HorizontalHeader,
b'vhea' : VerticalHeader,
b'maxp' : MaxpTable,
b'loca' : LocaTable,
b'glyf' : GlyfTable,
b'cmap' : CmapTable,
b'CFF ' : CFFTable,
b'kern' : KernTable,
b'GSUB' : GSUBTable,
b'OS/2' : OS2Table,
b'post' : PostTable,
}
def __init__(self, raw_or_get_table):
self.tables = {}
if isinstance(raw_or_get_table, bytes):
raw = raw_or_get_table
self.sfnt_version = raw[:4]
if self.sfnt_version not in {b'\x00\x01\x00\x00', b'OTTO', b'true',
b'type1'}:
raise UnsupportedFont('Font has unknown sfnt version: %r'%self.sfnt_version)
for table_tag, table, table_index, table_offset, table_checksum in get_tables(raw):
self.tables[table_tag] = self.TABLE_MAP.get(
table_tag, UnknownTable)(table)
else:
for table_tag in {
b'cmap', b'hhea', b'head', b'hmtx', b'maxp', b'name', b'OS/2',
b'post', b'cvt ', b'fpgm', b'glyf', b'loca', b'prep', b'CFF ',
b'VORG', b'EBDT', b'EBLC', b'EBSC', b'BASE', b'GSUB', b'GPOS',
b'GDEF', b'JSTF', b'gasp', b'hdmx', b'kern', b'LTSH', b'PCLT',
b'VDMX', b'vhea', b'vmtx', b'MATH'}:
table = bytes(raw_or_get_table(table_tag))
if table:
self.tables[table_tag] = self.TABLE_MAP.get(
table_tag, UnknownTable)(table)
if not self.tables:
raise UnsupportedFont('This font has no tables')
self.sfnt_version = (b'\0\x01\0\0' if b'glyf' in self.tables
else b'OTTO')
def __getitem__(self, key):
return self.tables[key]
def __contains__(self, key):
return key in self.tables
def __delitem__(self, key):
del self.tables[key]
def __iter__(self):
'''Iterate over the table tags in order.'''
yield from sorted(self.tables)
# Although the optimal order is not alphabetical, the OTF spec says
# they should be alphabetical, so we stick with that. See
# http://partners.adobe.com/public/developer/opentype/index_recs.html
# for optimal order.
# keys = list(self.tables)
# order = {x:i for i, x in enumerate((b'head', b'hhea', b'maxp', b'OS/2',
# b'hmtx', b'LTSH', b'VDMX', b'hdmx', b'cmap', b'fpgm', b'prep',
# b'cvt ', b'loca', b'glyf', b'CFF ', b'kern', b'name', b'post',
# b'gasp', b'PCLT', b'DSIG'))}
# keys.sort(key=lambda x:order.get(x, 1000))
# for x in keys:
# yield x
def pop(self, key, default=None):
return self.tables.pop(key, default)
def get(self, key, default=None):
return self.tables.get(key, default)
def sizes(self):
ans = OrderedDict()
for tag in self:
ans[tag] = len(self[tag])
return ans
def get_all_font_names(self):
from calibre.utils.fonts.metadata import get_font_names2, FontNames
name_table = self.get(b'name')
if name_table is not None:
return FontNames(*get_font_names2(name_table.raw, raw_is_table=True))
def __call__(self, stream=None):
stream = BytesIO() if stream is None else stream
def spack(*args):
stream.write(pack(*args))
stream.seek(0)
# Write header
num_tables = len(self.tables)
ln2 = max_power_of_two(num_tables)
srange = (2**ln2) * 16
spack(b'>4s4H',
self.sfnt_version, num_tables, srange, ln2, num_tables * 16 - srange)
# Write tables
head_offset = None
table_data = []
offset = stream.tell() + (calcsize(b'>4s3L') * num_tables)
sizes = OrderedDict()
for tag in self:
table = self.tables[tag]
raw = table()
table_len = len(raw)
if tag == b'head':
head_offset = offset
raw = raw[:8] + b'\0\0\0\0' + raw[12:]
raw = align_block(raw)
checksum = checksum_of_block(raw)
spack(b'>4s3L', tag, checksum, offset, table_len)
offset += len(raw)
table_data.append(raw)
sizes[tag] = table_len
for x in table_data:
stream.write(x)
checksum = checksum_of_block(stream.getvalue())
q = (0xB1B0AFBA - checksum) & 0xffffffff
stream.seek(head_offset + 8)
spack(b'>L', q)
return stream.getvalue(), sizes
def test_roundtrip(ff=None):
if ff is None:
data = P('fonts/liberation/LiberationSerif-Regular.ttf', data=True)
else:
with open(ff, 'rb') as f:
data = f.read()
rd = Sfnt(data)()[0]
verify_checksums(rd)
if data[:12] != rd[:12]:
raise ValueError('Roundtripping failed, font header not the same')
if len(data) != len(rd):
raise ValueError('Roundtripping failed, size different (%d vs. %d)'%
(len(data), len(rd)))
if __name__ == '__main__':
import sys
test_roundtrip(sys.argv[-1])