From e77df261744d74e4cfebda42b8fd032f414d084e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 28 Jul 2019 20:46:12 +0530 Subject: [PATCH] Fix merging of loca tables --- src/calibre/utils/fonts/sfnt/loca.py | 44 ++++++++++++++++---------- src/calibre/utils/fonts/sfnt/maxp.py | 2 +- src/calibre/utils/fonts/sfnt/merge.py | 10 ++++-- src/calibre/utils/fonts/sfnt/subset.py | 4 +-- 4 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/calibre/utils/fonts/sfnt/loca.py b/src/calibre/utils/fonts/sfnt/loca.py index f2827558be..ff1d2e7206 100644 --- a/src/calibre/utils/fonts/sfnt/loca.py +++ b/src/calibre/utils/fonts/sfnt/loca.py @@ -6,7 +6,7 @@ __license__ = 'GPL v3' __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' -from struct import calcsize, unpack_from, pack +import array, sys from operator import itemgetter from itertools import repeat @@ -14,16 +14,22 @@ from calibre.utils.fonts.sfnt import UnknownTable from polyglot.builtins import iteritems, range +def four_byte_type_code(): + for c in 'IL': + a = array.array(c) + if a.itemsize == 4: + return c + + class LocaTable(UnknownTable): def load_offsets(self, head_table, maxp_table): - fmt = 'H' if head_table.index_to_loc_format == 0 else 'L' - num_glyphs = maxp_table.num_glyphs - sz = calcsize(('>%s'%fmt).encode('ascii')) - num = len(self.raw)//sz - self.offset_map = unpack_from(('>%d%s'%(num, fmt)).encode('ascii'), - self.raw) - self.offset_map = self.offset_map[:num_glyphs+1] + fmt = 'H' if head_table.index_to_loc_format == 0 else four_byte_type_code() + locs = array.array(fmt) + locs.fromstring(self.raw) + if sys.byteorder != "big": + locs.byteswap() + self.offset_map = locs.tolist() if fmt == 'H': self.offset_map = [2*i for i in self.offset_map] self.fmt = fmt @@ -37,8 +43,14 @@ class LocaTable(UnknownTable): ''' Update this table to contain pointers only to the glyphs in resolved_glyph_map which must be a map of glyph_ids to (offset, sz) + Note that the loca table is generated for all glyphs from 0 to the + largest glyph that is either in resolved_glyph_map or was present + originally. The pointers to glyphs that have no data will be set to + zero. This preserves glyph ids. ''' + current_max_glyph_id = len(self.offset_map) - 2 max_glyph_id = max(resolved_glyph_map or (0,)) + max_glyph_id = max(max_glyph_id, current_max_glyph_id) self.offset_map = list(repeat(0, max_glyph_id + 2)) glyphs = [(glyph_id, x[0], x[1]) for glyph_id, x in iteritems(resolved_glyph_map)] @@ -54,16 +66,16 @@ class LocaTable(UnknownTable): vals = self.offset_map max_offset = max(vals) if vals else 0 - max_short_offset = 65535 * 2 - if self.fmt == 'L' and max_offset <= max_short_offset: + if max_offset < 0x20000 and all(l % 2 == 0 for l in vals): self.fmt = 'H' - if self.fmt == 'H': - if max_offset > max_short_offset: - self.fmt = 'L' - else: - vals = [i//2 for i in vals] + vals = array.array(self.fmt, (i // 2 for i in vals)) + else: + self.fmt = four_byte_type_code() + vals = array.array(self.fmt, vals) - self.raw = pack(('>%d%s'%(len(vals), self.fmt)).encode('ascii'), *vals) + if sys.byteorder != "big": + vals.byteswap() + self.raw = vals.tostring() subset = update def dump_glyphs(self, sfnt): diff --git a/src/calibre/utils/fonts/sfnt/maxp.py b/src/calibre/utils/fonts/sfnt/maxp.py index fd70b16a7b..4d927ee7a4 100644 --- a/src/calibre/utils/fonts/sfnt/maxp.py +++ b/src/calibre/utils/fonts/sfnt/maxp.py @@ -42,5 +42,5 @@ class MaxpTable(UnknownTable): setattr(self, f, val) def update(self): - vals = [getattr(self, f) for f in self._fields] + vals = [getattr(self, f) for f in self.fields] self.raw = pack(self._fmt, *vals) diff --git a/src/calibre/utils/fonts/sfnt/merge.py b/src/calibre/utils/fonts/sfnt/merge.py index 6ef28eeda8..326156b8fd 100644 --- a/src/calibre/utils/fonts/sfnt/merge.py +++ b/src/calibre/utils/fonts/sfnt/merge.py @@ -11,6 +11,7 @@ from functools import partial def merge_truetype_fonts_for_pdf(*fonts): # only merges the glyf and loca tables, ignoring all other tables all_glyphs = {} + ans = fonts[0] for font in fonts: loca = font[b'loca'] glyf = font[b'glyf'] @@ -21,12 +22,17 @@ def merge_truetype_fonts_for_pdf(*fonts): if sz > 0: all_glyphs[glyph_id] = glyf.glyph_data(offset, sz, as_raw=True) - ans = fonts[0] - loca = ans[b'loca'] glyf = ans[b'glyf'] + head = ans[b'head'] + loca = ans[b'loca'] + maxp = ans[b'maxp'] gmap = OrderedDict() for glyph_id in sorted(all_glyphs): gmap[glyph_id] = partial(all_glyphs.__getitem__, glyph_id) offset_map = glyf.update(gmap) loca.update(offset_map) + head.index_to_loc_format = 0 if loca.fmt == 'H' else 1 + head.update() + maxp.num_glyphs = len(loca.offset_map) - 1 + maxp.update() return ans diff --git a/src/calibre/utils/fonts/sfnt/subset.py b/src/calibre/utils/fonts/sfnt/subset.py index bb31869517..cdc46f0a2e 100644 --- a/src/calibre/utils/fonts/sfnt/subset.py +++ b/src/calibre/utils/fonts/sfnt/subset.py @@ -64,9 +64,9 @@ def subset_truetype(sfnt, character_map, extra_glyphs): # Update the loca table loca.subset(glyph_offset_map) - head.index_to_loc_format = 1 if loca.fmt == 'L' else 0 + head.index_to_loc_format = 0 if loca.fmt == 'H' else 1 head.update() - maxp.num_glyphs = len(glyph_offset_map) + maxp.num_glyphs = len(loca.offset_map) - 1 # }}}