1#!/usr/local/bin/python3.8 2# vim:fileencoding=utf-8 3# License: GPL v3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net> 4 5 6from collections import OrderedDict 7from functools import partial 8 9 10class GlyphSizeMismatch(ValueError): 11 pass 12 13 14def merge_truetype_fonts_for_pdf(fonts, log=None): 15 all_glyphs = {} 16 ans = fonts[0] 17 hmetrics_map = {} 18 vmetrics_map = {} 19 20 for font in fonts: 21 loca = font[b'loca'] 22 glyf = font[b'glyf'] 23 num_glyphs = font[b'maxp'].num_glyphs 24 loca.load_offsets(font[b'head'], font[b'maxp']) 25 try: 26 hhea = font[b'hhea'] 27 except KeyError: 28 hhea = None 29 else: 30 hhea.read_data(font[b'hmtx'], num_glyphs) 31 try: 32 vhea = font[b'vhea'] 33 except KeyError: 34 vhea = None 35 else: 36 vhea.read_data(font[b'vmtx'], num_glyphs) 37 38 for glyph_id in range(len(loca.offset_map) - 1): 39 offset, sz = loca.glyph_location(glyph_id) 40 prev_glyph_data = all_glyphs.get(glyph_id) 41 if not prev_glyph_data: 42 all_glyphs[glyph_id] = glyf.glyph_data(offset, sz, as_raw=True) 43 if hhea is not None: 44 hmetrics_map[glyph_id] = hhea.metrics_for(glyph_id) 45 if vhea is not None: 46 vmetrics_map[glyph_id] = vhea.metrics_for(glyph_id) 47 elif sz > 0: 48 if abs(sz - len(prev_glyph_data)) > 8: 49 # raise Exception('Size mismatch for glyph id: {} prev_sz: {} sz: {}'.format(glyph_id, len(prev_glyph_data), sz)) 50 if log is not None: 51 log('Size mismatch for glyph id: {} prev_sz: {} sz: {}'.format(glyph_id, len(prev_glyph_data), sz)) 52 if hhea is not None: 53 m = hhea.metrics_for(glyph_id) 54 if m != hmetrics_map[glyph_id]: 55 log(f'Metrics mismatch for glyph id: {glyph_id} prev: {hmetrics_map[glyph_id]} cur: {m}') 56 if vhea is not None: 57 m = vhea.metrics_for(glyph_id) 58 if m != vmetrics_map[glyph_id]: 59 log(f'Metrics mismatch for glyph id: {glyph_id} prev: {vmetrics_map[glyph_id]} cur: {m}') 60 61 glyf = ans[b'glyf'] 62 head = ans[b'head'] 63 loca = ans[b'loca'] 64 maxp = ans[b'maxp'] 65 66 gmap = OrderedDict() 67 for glyph_id in sorted(all_glyphs): 68 gmap[glyph_id] = partial(all_glyphs.__getitem__, glyph_id) 69 offset_map = glyf.update(gmap) 70 loca.update(offset_map) 71 head.index_to_loc_format = 0 if loca.fmt == 'H' else 1 72 head.update() 73 maxp.num_glyphs = len(loca.offset_map) - 1 74 maxp.update() 75 if hmetrics_map: 76 ans[b'hhea'].update(hmetrics_map, ans[b'hmtx']) 77 if vmetrics_map: 78 ans[b'vhea'].update(vmetrics_map, ans[b'vmtx']) 79 80 for name in 'hdmx GPOS GSUB'.split(): 81 ans.pop(name.encode('ascii'), None) 82 return ans 83