diff --git a/src/calibre/ebooks/pdf/html_writer.py b/src/calibre/ebooks/pdf/html_writer.py index 8552a89286..761571a724 100644 --- a/src/calibre/ebooks/pdf/html_writer.py +++ b/src/calibre/ebooks/pdf/html_writer.py @@ -657,6 +657,22 @@ class Range(object): return len(self.widths) == 1 +def all_glyph_ids_in_w_arrays(arrays): + ans = set() + for w in arrays: + i = 0 + while i + 1 < len(w): + elem = w[i] + next_elem = w[i+1] + if isinstance(next_elem, list): + ans |= set(range(elem, elem + len(next_elem))) + i += 2 + else: + ans |= set(range(elem, next_elem + 1)) + i += 3 + return sorted(ans) + + def merge_w_arrays(arrays): ranges = [] for w in arrays: @@ -822,10 +838,18 @@ def merge_font(fonts): cmaps = list(filter(None, (f['ToUnicode'] for f in t0_fonts))) if cmaps: t0_font['ToUnicode'] = as_bytes(merge_cmaps(cmaps)) - for key in ('W', 'W2'): - arrays = tuple(filter(None, (f[key] for f in descendant_fonts))) - base_font[key] = merge_w_arrays(arrays) - base_font['sfnt'] = merge_truetype_fonts_for_pdf(*(f['sfnt'] for f in descendant_fonts)) + base_font['sfnt'], width_for_glyph_id, height_for_glyph_id = merge_truetype_fonts_for_pdf(*(f['sfnt'] for f in descendant_fonts)) + widths = [] + arrays = tuple(filter(None, (f['W'] for f in descendant_fonts))) + if arrays: + for gid in all_glyph_ids_in_w_arrays(arrays): + widths.append(gid), widths.append(gid), widths.append(1000*width_for_glyph_id(gid)) + base_font['W'] = merge_w_arrays((widths,)) + arrays = tuple(filter(None, (f['W2'] for f in descendant_fonts))) + if arrays: + for gid in all_glyph_ids_in_w_arrays(arrays): + widths.append(gid), widths.append(gid), widths.append(1000*height_for_glyph_id(gid)) + base_font['W2'] = merge_w_arrays((widths,)) return t0_font, base_font, references_to_drop diff --git a/src/calibre/utils/fonts/sfnt/container.py b/src/calibre/utils/fonts/sfnt/container.py index bac85f17e7..394a9572ac 100644 --- a/src/calibre/utils/fonts/sfnt/container.py +++ b/src/calibre/utils/fonts/sfnt/container.py @@ -1,29 +1,25 @@ #!/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 from __future__ import absolute_import, division, print_function, unicode_literals -__license__ = 'GPL v3' -__copyright__ = '2012, Kovid Goyal ' -__docformat__ = 'restructuredtext en' - -from struct import pack, calcsize -from io import BytesIO from collections import OrderedDict +from io import BytesIO +from struct import calcsize, pack -from calibre.utils.fonts.utils import (get_tables, checksum_of_block, - verify_checksums) -from calibre.utils.fonts.sfnt import align_block, UnknownTable, max_power_of_two -from calibre.utils.fonts.sfnt.errors import UnsupportedFont - -from calibre.utils.fonts.sfnt.head import (HeadTable, HorizontalHeader, - OS2Table, PostTable) -from calibre.utils.fonts.sfnt.maxp import MaxpTable -from calibre.utils.fonts.sfnt.loca import LocaTable -from calibre.utils.fonts.sfnt.glyf import GlyfTable -from calibre.utils.fonts.sfnt.cmap import CmapTable -from calibre.utils.fonts.sfnt.kern import KernTable -from calibre.utils.fonts.sfnt.gsub import GSUBTable +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 +) +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 @@ -100,6 +96,9 @@ class Sfnt(object): 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: diff --git a/src/calibre/utils/fonts/sfnt/head.py b/src/calibre/utils/fonts/sfnt/head.py index 5abb0c5cf0..a9a5c69113 100644 --- a/src/calibre/utils/fonts/sfnt/head.py +++ b/src/calibre/utils/fonts/sfnt/head.py @@ -10,6 +10,7 @@ from struct import unpack_from, pack, calcsize from calibre.utils.fonts.sfnt import UnknownTable, DateTimeProperty, FixedProperty from calibre.utils.fonts.sfnt.errors import UnsupportedFont +from calibre.utils.fonts.sfnt.loca import read_array from polyglot.builtins import zip @@ -67,7 +68,7 @@ class HorizontalHeader(UnknownTable): 'descender', 'h', 'line_gap', 'h', 'advance_width_max', 'H', - 'min_left_size_bearing', 'h', + 'min_left_side_bearing', 'h', 'min_right_side_bearing', 'h', 'x_max_extent', 'h', 'caret_slope_rise', 'h', @@ -92,12 +93,55 @@ class HorizontalHeader(UnknownTable): if len(raw) < 4*num: raise UnsupportedFont('The hmtx table has insufficient data') long_hor_metric = raw[:4*num] - fmt = '>%dH'%(2*num) - entries = unpack_from(fmt.encode('ascii'), long_hor_metric) - self.advance_widths = entries[0::2] - fmt = '>%dh'%(2*num) - entries = unpack_from(fmt.encode('ascii'), long_hor_metric) - self.left_side_bearings = entries[1::2] + a = read_array(long_hor_metric) + self.advance_widths = a[0::2] + a = read_array(long_hor_metric, 'h') + self.left_side_bearings = a[1::2] + + +class VericalHeader(UnknownTable): + + version_number = FixedProperty('_version_number') + + def read_data(self, vmtx): + if hasattr(self, 'ascender'): + return + field_types = ( + '_version_number' , 'l', + 'ascender', 'h', + 'descender', 'h', + 'line_gap', 'h', + 'advance_height_max', 'H', + 'min_top_side_bearing', 'h', + 'min_bottom_side_bearing', 'h', + 'y_max_extent', 'h', + 'caret_slope_rise', 'h', + 'caret_slop_run', 'h', + 'caret_offset', 'h', + 'r1', 'h', + 'r2', 'h', + 'r3', 'h', + 'r4', 'h', + 'metric_data_format', 'h', + 'number_of_v_metrics', 'H', + ) + + self._fmt = ('>%s'%(''.join(field_types[1::2]))).encode('ascii') + self._fields = field_types[0::2] + + for f, val in zip(self._fields, unpack_from(self._fmt, self.raw)): + setattr(self, f, val) + + raw = vmtx.raw + num = self.number_of_h_metrics + if len(raw) < 4*num: + raise UnsupportedFont('The vmtx table has insufficient data') + long_hor_metric = raw[:4*num] + long_hor_metric = raw[:4*num] + a = read_array(long_hor_metric) + self.advance_heights = a[0::2] + a = read_array(long_hor_metric, 'h') + self.top_side_bearings = a[1::2] class OS2Table(UnknownTable): diff --git a/src/calibre/utils/fonts/sfnt/loca.py b/src/calibre/utils/fonts/sfnt/loca.py index ff1d2e7206..04a9c37480 100644 --- a/src/calibre/utils/fonts/sfnt/loca.py +++ b/src/calibre/utils/fonts/sfnt/loca.py @@ -21,14 +21,18 @@ def four_byte_type_code(): return c +def read_array(data, fmt='H'): + ans = array.array(fmt, data) + if sys.byteorder != 'big': + ans.byteswap() + return ans + + class LocaTable(UnknownTable): def load_offsets(self, head_table, maxp_table): 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() + locs = read_array(self.raw, fmt) self.offset_map = locs.tolist() if fmt == 'H': self.offset_map = [2*i for i in self.offset_map] diff --git a/src/calibre/utils/fonts/sfnt/merge.py b/src/calibre/utils/fonts/sfnt/merge.py index 163a60f227..8c73d576ea 100644 --- a/src/calibre/utils/fonts/sfnt/merge.py +++ b/src/calibre/utils/fonts/sfnt/merge.py @@ -30,6 +30,26 @@ def merge_truetype_fonts_for_pdf(*fonts): head = ans[b'head'] loca = ans[b'loca'] maxp = ans[b'maxp'] + advance_widths = advance_heights = (0,) + hhea = ans.get(b'hhea') + if hhea is not None: + hhea.read_data(ans[b'hmtx']) + advance_widths = tuple(x/head.units_per_em for x in hhea.advance_widths) + vhea = ans.get(b'vhea') + if vhea is not None: + vhea.read_data(ans[b'vmtx']) + advance_heights = tuple(x/head.units_per_em for x in hhea.advance_heights) + + def width_for_glyph_id(gid): + if gid >= len(advance_widths): + gid = -1 + return advance_widths[gid] + + def height_for_glyph_id(gid): + if gid >= len(advance_widths): + gid = -1 + return advance_heights[gid] + gmap = OrderedDict() for glyph_id in sorted(all_glyphs): gmap[glyph_id] = partial(all_glyphs.__getitem__, glyph_id) @@ -39,4 +59,4 @@ def merge_truetype_fonts_for_pdf(*fonts): head.update() maxp.num_glyphs = len(loca.offset_map) - 1 maxp.update() - return ans + return ans, width_for_glyph_id, height_for_glyph_id