From 24ac765178f2217fe199d2f7b96046ed980cf634 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 11 Dec 2020 17:07:04 +0530 Subject: [PATCH] PDF Output: yet another glyph width fix for change in Chromium output Apparently, the HarfBuzz subsetter does not produce mergeable hhea tables, so we directly merge the W arrays instead, picking the widest width for each glyph id. Lord knows if the glyph ids are stable across fonts, if they aren't then we will need to do major surgery. Fixes #1907675 [MacOS 10.15.7 EPUB to PDF conversion](https://bugs.launchpad.net/calibre/+bug/1907675) --- src/calibre/ebooks/pdf/html_writer.py | 48 ++++++++++++++++++++++----- src/calibre/utils/fonts/sfnt/merge.py | 22 ++---------- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/src/calibre/ebooks/pdf/html_writer.py b/src/calibre/ebooks/pdf/html_writer.py index 7c58c7a164..71e2a4d3ac 100644 --- a/src/calibre/ebooks/pdf/html_writer.py +++ b/src/calibre/ebooks/pdf/html_writer.py @@ -799,6 +799,43 @@ def merge_w_arrays(arrays): return ans +def width_map_from_w_array(w): + ans = {} + i = 0 + while i + 1 < len(w): + elem = w[i] + next_elem = w[i+1] + if isinstance(next_elem, list): + for gid, width in zip(range(elem, elem + len(next_elem)), next_elem): + ans[gid] = width + i += 2 + else: + try: + width = w[i+2] + except IndexError: + width = 0 + for gid in range(elem, next_elem + 1): + ans[gid] = width + i += 3 + return ans + + +def merge_w_arrays_directly(arrays): + width_maps = tuple(map(width_map_from_w_array, arrays)) + + def getter(gid): + return max(m.get(gid, 0) for m in width_maps) + + all_gids = set() + for m in width_maps: + all_gids |= set(m) + + widths = [] + for gid in sorted(all_gids): + widths.extend((gid, gid, getter(gid))) + return merge_w_arrays((widths,)) + + class CMap(object): def __init__(self): @@ -933,18 +970,13 @@ def merge_font(fonts, log): cmaps = list(filter(None, (f['ToUnicode'] for f in t0_fonts))) if cmaps: t0_font['ToUnicode'] = as_bytes(merge_cmaps(cmaps)) - base_font['sfnt'], width_for_glyph_id, height_for_glyph_id = merge_truetype_fonts_for_pdf(tuple(f['sfnt'] for f in descendant_fonts), log) - widths = [] + base_font['sfnt'] = merge_truetype_fonts_for_pdf(tuple(f['sfnt'] for f in descendant_fonts), log) 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,)) + base_font['W'] = merge_w_arrays_directly(arrays) 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,)) + base_font['W2'] = merge_w_arrays_directly(arrays) return t0_font, base_font, references_to_drop diff --git a/src/calibre/utils/fonts/sfnt/merge.py b/src/calibre/utils/fonts/sfnt/merge.py index f8772fd8a0..01ed837938 100644 --- a/src/calibre/utils/fonts/sfnt/merge.py +++ b/src/calibre/utils/fonts/sfnt/merge.py @@ -15,6 +15,7 @@ def merge_truetype_fonts_for_pdf(fonts, log=None): # 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'] @@ -35,25 +36,6 @@ def merge_truetype_fonts_for_pdf(fonts, log=None): 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 vhea.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_heights): - gid = -1 - return advance_heights[gid] gmap = OrderedDict() for glyph_id in sorted(all_glyphs): @@ -64,4 +46,4 @@ def merge_truetype_fonts_for_pdf(fonts, log=None): head.update() maxp.num_glyphs = len(loca.offset_map) - 1 maxp.update() - return ans, width_for_glyph_id, height_for_glyph_id + return ans