%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)