mirror of
https://github.com/gryf/ebook-converter.git
synced 2026-04-23 14:41:30 +02:00
Added docx writer related modules
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
#!/usr/bin/env python2
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
|
||||
# License: GPLv3 Copyright: 2012, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
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(object):
|
||||
|
||||
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.'''
|
||||
for x in sorted(self.tables):
|
||||
yield x
|
||||
# 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 __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])
|
||||
Reference in New Issue
Block a user