%PDF- %PDF-
Direktori : /usr/lib/calibre/calibre/ebooks/pdf/render/ |
Current File : //usr/lib/calibre/calibre/ebooks/pdf/render/fonts.py |
#!/usr/bin/env python3 __license__ = 'GPL v3' __copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>' __docformat__ = 'restructuredtext en' import re from itertools import groupby from operator import itemgetter from collections import Counter, OrderedDict from polyglot.builtins import iteritems, codepoint_to_chr from calibre import as_unicode from calibre.ebooks.pdf.render.common import (Array, String, Stream, Dictionary, Name) from calibre.utils.fonts.sfnt.subset import pdf_subset, UnsupportedFont, NoGlyphs from calibre.utils.short_uuid import uuid4 STANDARD_FONTS = { 'Times-Roman', 'Helvetica', 'Courier', 'Symbol', 'Times-Bold', 'Helvetica-Bold', 'Courier-Bold', 'ZapfDingbats', 'Times-Italic', 'Helvetica-Oblique', 'Courier-Oblique', 'Times-BoldItalic', 'Helvetica-BoldOblique', 'Courier-BoldOblique', } ''' Notes ======= We must use Type 0 CID keyed fonts to represent unicode text. For TrueType -------------- The mapping from the text strings to glyph ids is defined by two things: The /Encoding key of the Type-0 font dictionary The /CIDToGIDMap key of the descendant font dictionary (for TrueType fonts) We set /Encoding to /Identity-H and /CIDToGIDMap to /Identity. This means that text strings are interpreted as a sequence of two-byte numbers, high order byte first. Each number gets mapped to a glyph id equal to itself by the /CIDToGIDMap. ''' import textwrap class FontStream(Stream): def __init__(self, is_otf, compress=False): Stream.__init__(self, compress=compress) self.is_otf = is_otf def add_extra_keys(self, d): d['Length1'] = d['DL'] if self.is_otf: d['Subtype'] = Name('CIDFontType0C') def to_hex_string(c): ans = hex(int(c))[2:] if isinstance(ans, bytes): ans = ans.decode('ascii') return ans.rjust(4, '0') class CMap(Stream): skeleton = textwrap.dedent('''\ /CIDInit /ProcSet findresource begin 12 dict begin begincmap /CMapName {name}-cmap def /CMapType 2 def /CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def 1 begincodespacerange <0000> <FFFF> endcodespacerange {mapping} endcmap CMapName currentdict /CMap defineresource pop end end ''') def __init__(self, name, glyph_map, compress=False): Stream.__init__(self, compress) current_map = OrderedDict() maps = [] for glyph_id in sorted(glyph_map): if len(current_map) > 99: maps.append(current_map) current_map = OrderedDict() val = [] for c in glyph_map[glyph_id]: c = ord(c) val.append(to_hex_string(c)) glyph_id = '<%s>'%to_hex_string(glyph_id) current_map[glyph_id] = '<%s>'%''.join(val) if current_map: maps.append(current_map) mapping = [] for m in maps: meat = '\n'.join('%s %s'%(k, v) for k, v in iteritems(m)) mapping.append('%d beginbfchar\n%s\nendbfchar'%(len(m), meat)) try: name = name.encode('ascii').decode('ascii') except Exception: name = uuid4() self.write(self.skeleton.format(name=name, mapping='\n'.join(mapping))) class Font: def __init__(self, metrics, num, objects, compress): self.metrics, self.compress = metrics, compress self.is_otf = self.metrics.is_otf self.subset_tag = str( re.sub('.', lambda m: codepoint_to_chr(int(m.group())+ord('A')), oct(num).replace('o', '') )).rjust(6, 'A') self.font_stream = FontStream(metrics.is_otf, compress=compress) try: psname = metrics.postscript_name except Exception: psname = uuid4() self.font_descriptor = Dictionary({ 'Type': Name('FontDescriptor'), 'FontName': Name('%s+%s'%(self.subset_tag, psname)), 'Flags': 0b100, # Symbolic font 'FontBBox': Array(metrics.pdf_bbox), 'ItalicAngle': metrics.post.italic_angle, 'Ascent': metrics.pdf_ascent, 'Descent': metrics.pdf_descent, 'CapHeight': metrics.pdf_capheight, 'AvgWidth': metrics.pdf_avg_width, 'StemV': metrics.pdf_stemv, }) self.descendant_font = Dictionary({ 'Type':Name('Font'), 'Subtype':Name('CIDFontType' + ('0' if metrics.is_otf else '2')), 'BaseFont': self.font_descriptor['FontName'], 'FontDescriptor':objects.add(self.font_descriptor), 'CIDSystemInfo':Dictionary({ 'Registry':String('Adobe'), 'Ordering':String('Identity'), 'Supplement':0, }), }) if not self.is_otf: self.descendant_font['CIDToGIDMap'] = Name('Identity') self.font_dict = Dictionary({ 'Type':Name('Font'), 'Subtype':Name('Type0'), 'Encoding':Name('Identity-H'), 'BaseFont':self.descendant_font['BaseFont'], 'DescendantFonts':Array([objects.add(self.descendant_font)]), }) self.used_glyphs = set() def embed(self, objects, debug): self.font_descriptor['FontFile'+('3' if self.is_otf else '2') ] = objects.add(self.font_stream) self.write_widths(objects) self.write_to_unicode(objects) try: pdf_subset(self.metrics.sfnt, self.used_glyphs) except UnsupportedFont as e: debug('Subsetting of %s not supported, embedding full font. Error: %s'%( self.metrics.names.get('full_name', 'Unknown'), as_unicode(e))) except NoGlyphs: if self.used_glyphs: debug( 'Subsetting of %s failed, font appears to have no glyphs for the %d characters it is used with, some text may not be rendered in the PDF' % (self.metrics.names.get('full_name', 'Unknown'), len(self.used_glyphs))) if self.is_otf: self.font_stream.write(self.metrics.sfnt['CFF '].raw) else: self.metrics.os2.zero_fstype() self.metrics.sfnt(self.font_stream) def write_to_unicode(self, objects): try: name = self.metrics.postscript_name except KeyError: name = uuid4() cmap = CMap(name, self.metrics.glyph_map, compress=self.compress) self.font_dict['ToUnicode'] = objects.add(cmap) def write_widths(self, objects): glyphs = sorted(self.used_glyphs|{0}) widths = {g:self.metrics.pdf_scale(w) for g, w in zip(glyphs, self.metrics.glyph_widths(glyphs))} counter = Counter() for g, w in iteritems(widths): counter[w] += 1 most_common = counter.most_common(1)[0][0] self.descendant_font['DW'] = most_common widths = {g:w for g, w in iteritems(widths) if w != most_common} groups = Array() for k, g in groupby(enumerate(widths), lambda i_x:i_x[0]-i_x[1]): group = list(map(itemgetter(1), g)) gwidths = [widths[g] for g in group] if len(set(gwidths)) == 1 and len(group) > 1: w = (min(group), max(group), gwidths[0]) else: w = (min(group), Array(gwidths)) groups.extend(w) self.descendant_font['W'] = objects.add(groups) class FontManager: def __init__(self, objects, compress): self.objects = objects self.compress = compress self.std_map = {} self.font_map = {} self.fonts = [] def add_font(self, font_metrics, glyph_ids): if font_metrics not in self.font_map: self.fonts.append(Font(font_metrics, len(self.fonts), self.objects, self.compress)) d = self.objects.add(self.fonts[-1].font_dict) self.font_map[font_metrics] = (d, self.fonts[-1]) fontref, font = self.font_map[font_metrics] font.used_glyphs |= glyph_ids return fontref def add_standard_font(self, name): if name not in STANDARD_FONTS: raise ValueError('%s is not a standard font'%name) if name not in self.std_map: self.std_map[name] = self.objects.add(Dictionary({ 'Type':Name('Font'), 'Subtype':Name('Type1'), 'BaseFont':Name(name) })) return self.std_map[name] def embed_fonts(self, debug): for font in self.fonts: font.embed(self.objects, debug)