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